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++: časové razítko

Dnes se podíváme na další volitelnou položku IP záhlaví, na volbu "časové razítko". Existují tři varianty položky "časové razítko". Dvě si ukážeme v příkladu a o třetí se okrajově zmíníme.

Dnes se podrobněji podíváme na další z nepovinných položek IP hlavičky, na položku „časové razítko“. IP paket putující po síti si nechává od každého směrovače, jímž projde, zapsat čas, ve kterém směrovačem prochází. Tímto způsobem lze získat informace o rychlosti, jakou putují IP pakety po jednotlivých linkách mezi směrovači.

Oproti položce „zaznamenávej směrovače“ přibyl v hlavičce jeden byte s příznaky. Jedná se o čtvrtý byte v nepovinné části IP hlavičky (viz formát nepovinné části IP hlavičky typu „časové razítko“). Tento byte příznaků je rozdělen na dvě poloviny (po 4 bitech).

První 4 bity indikují přetečení (nedostatek buněk). Při odesílání nastavíme na 0. Paket bude putovat sítí, na každém směrovači obdrží časový údaj a jde dále. Jestliže v IP hlavičce již není místo pro další záznam, dojde k přetečení a směrovač zvýší příznak o 1. Tím pádem v cíli má tento příznak hodnotu udávající počet směrovačů, které nemohly zapsat časový údaj, protože nebylo kam.

Druhé 4 bity udávají způsob, jakým směrovače budou zapisovat a co budou zapisovat. Možné hodnoty a názvy maker, které reprezentují dané hodnoty, jsem uvedl v článku Sokety a C/C++ – volitelné položky IP záhlaví. My si v dnešním příkladu ukážeme možnosti „pouze časové razítko“ a „časové razítko a IP adresy“.

Pouze časové razítko

Každá buňka bude mít 4 byty – použijeme datový typ unsigned int. Bude obsahovat časový údaj – počet milisekund od poslední půlnoci světového času. Jestliže směrovač nezapíše časový údaj ve standardním tvaru (tedy číslo neudává počet milisekund od poslední půlnoci), je nastaven nejvyšší bit na 1. Při zpracovávání tohoto údaje nesmíme zapomenout, že číslo udávající milisekundy přijde v „síťovém“ tvaru. Chceme-li s údajem pracovat, musíme jej nejprve pomocí funkce ntohl převést do „normálního“ tvaru.

Časové razítko a IP adresy

Každá buňka bude mít 8 bytů. První 4 byty udávají IP adresu směrovače, který vkládá razítko. Druhé 4 byty buňky udávají časové razítko, pro které platí vše co v případě, kdy zaznamenáváme pouze časová razítka.

Vybrané směrovače

Existuje také možnost vybrat směrovače, jejichž časové razítko nás zajímá. V takovém případě bude mít opět buňka 8 bytů a první 4 byty budou obsahovat IP adresu směrovače, který má zapsat časová razítko. Druhé 4 byty budou nastaveny na 0. Projde-li IP paket směrovačem, jehož IP adresa je uvedena v nějaké buňce, zapíše do druhé poloviny své časové razítko. Není-li v IP hlavičce buňka s jeho IP adresou, nezapíše nic.

Příklad

Vytvoříme si jednoduchý příklad, který bude využívat možnosti „pouze časová razítka“ a „časová razítka a adresy“. Program opět odešle ICMP žádost o ECHO a počká na ICMP ECHO odpověď. ICMP paket bude v IP paketu s IP hlavičkou rozšířenou o nepovinnou položku „časové razítko“ (time stamp).

Formality

Jako vždy musíme vložit hlavičkové soubory, deklarovat proměnné a podobně. Abych se v každém článku neopakoval, zahrnu do „formalit“ také přeložení doménového jména, vytvoření soketu, zaplnění instance struktury sockaddr_in. Vše se v každém článku opakuje. Jen chci upozornit, že program může mít nepovinný druhý parametr -ipaddr, po jehož zadání se bude na směrovačích zapisovat nejen čas, ale také IP adresa. V případě, že druhý parametr programu není zadán nebo má jinou hodnotu, zapisují se pouze časy.

Za povšimnutí stojí jen proměnná fieldLength udávající velikost buňky. Implicitně má hodnotu 4, jestliže je druhým parametrem -ipaddr, zvýšíme ji na 8. Později budeme s proměnnou počítat.

#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/udp.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>

#include <string.h>
#include <errno.h>

#define MAX 65536

using namespace std;

unsigned short checksum(unsigned char *addr, int count);
void getTime(unsigned int ms, unsigned int &h,
    unsigned int &m, unsigned int &s);

int main(int argc, char *argv[])
{
  size_t size;
  hostent *host;
  icmphdr icmp, *icmpRecv;
  iphdr *ipRecv;
  int sock, lenght;
  sockaddr_in sendSockAddr;
  fd_set mySet;
  timeval tv;
  char *addrString;
  unsigned short int pid = getpid();
  char buffer[MAX];
  char ipOptions[MAX_IPOPTLEN];
  unsigned char *ipOptionsRecv;
  bool recv = false, onlyTime = true;
  unsigned int *time, ms, hour = 0, min = 0, sec = 0
  unsigned int fieldLength = 4;

  if ((argc < 2) || (argc > 3))
  {
    cerr << "Syntaxe:\n\t" << argv[0]
     << "adresa -ipaddr" << endl;
    return -1;
  }
  if ((argc == 3)
    && (strncmp(argv[2], "-ipaddr", 8) == 0))
  {
    onlyTime = false;
    fieldLength = 8;
  }
  // Zjistíme informace o vzdáleném počítači
  if ((host = gethostbyname(argv[1])) == NULL)
  {
    cerr << "Špatná cílová adresa" << endl;
    return -1;
  }
  // Vytvoříme soket
  if ((sock =
    socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
  {
    cerr << "Nelze vytvořit soket" << endl;
    return -1;
  }
  // Připravime strukturu pro sendto
  sendSockAddr.sin_family = AF_INET;
  sendSockAddr.sin_port = 0;
  memcpy(&(sendSockAddr.sin_addr), host->

    h_addr, host->h_length);

ICMP hlavička

Vyplníme ICMP hlavičku ECHO žádosti.

  icmp.type = ICMP_ECHO;
  icmp.code = 0;
  icmp.un.echo.id = pid;
  icmp.checksum = 0;
  icmp.un.echo.sequence = 1;
  icmp.checksum = checksum((unsigned char *)&icmp,
     sizeof(icmphdr));

Volitelné položky IP hlavičky

Zaplníme pole ipOptions volitelnou položkou IP záhlaví. Nejprve vynulujeme pole a poté začneme zapisovat údaje.

  memset(ipOptions, 0, MAX_IPOPTLEN);

Typ položky

Typ položky je 1 byte pole (prvek s indexem 0). Jeho hodnota musí být 63 (makro IPOPT_TS).

  ipOptions[IPOPT_OPTVAL] = IPOPT_TS;

Velikost položky

Velikost budeme mít maximální možnou – 40 bytů – hodnota makra MAX_IPOPTLEN. Velikost položky se zapisuje do druhého byte.

  ipOptions[IPOPT_OLEN] = MAX_IPOPTLEN;

Offset první buňky

První buňka bude posunutá od začátku 5 bytů. Zde je malý rozdíl od minula, kdy bylo posunutí 4 byty. Nyní totiž přibyl jeden byte s příznaky. Makro IPOPT_MINOFF je definováno jako 4, což je nejmenší možné posunutí. Posunutí se zapisuje na 3. byte od začátku.

  ipOptions[IPOPT_OFFSET] = IPOPT_MINOFF + 1;

Příznaky OF a FL

První 4 bity obsahují hodnotu OF – příznak přetečení. Při odesílání nastavujeme na 0. Druhé 4 bity obsahují příznak FL. Příznak určuje, zda se mají zaznamenávat jen časy, nebo i IP adresy, nebo jen časy na vybraných IP adresách. My nastavíme hodnotu makra IPOPT_TS_TSONLY nebo IPOPT_TS_TSAN­DADDR podle toho, zda bylo z příkazové řádky zadáno -ipaddr.

  ipOptions[IPOPT_OFFSET + 1] =
    (onlyTime? IPOPT_TS_TSONLY : IPOPT_TS_TSANDADDR);

Nastavení volitelných položek IP záhlaví

Pomocí setsockopt nastavíme naše pole jako rozšířenou část IP hlavičky, kterou budou obsahovat pakety posílané naším soketem.

  if (setsockopt(sock, SOL_IP, IP_OPTIONS, ipOptions,
    MAX_IPOPTLEN) == -1)
  {
    cout << "Nelze nastavit \"Time stamp\"" << endl;
    close(sock);
    return -1;
  }

Odeslání dat

  if ((sendto(sock, (char *)&icmp, sizeof(icmphdr), 0,
    (sockaddr *)&sendSockAddr,sizeof(sockaddr))==-1))
  {
    cerr << "Problém s odesláním dat" << endl;
    close(sock);
    return -1;
  }

Přijetí dat

Tak, jak jsme si ukazovali již mnohokrát, přijmeme pomocí select a následného recvfrom data.

  do
  {
    FD_ZERO(&mySet);
    FD_SET(sock, &mySet);
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    if (select(sock + 1, &mySet, NULL, NULL, &tv) < 0)
    {
      cerr << "Selhal select" << endl;
      break;
    }
    if (FD_ISSET(sock, &mySet))
    {
      size = sizeof(sendSockAddr);
      if ((lenght = recvfrom(sock, buffer, MAX, 0,
        (sockaddr *)&sendSockAddr, &size)) == -1)
      {
    close(sock);
    return -1;
      }
      cout << "Přijato " << lenght << " bytů" << endl;

Nastavení ukazatelů

Za povinnou částí IP hlavičky (možná) následuje nepovinná část IP hlavičky. Za celou IP hlavičkou následuje ICMP hlavička. Poloha začátku ICMP hlavičky v bufferu je dána čtyřnásobkem hodnoty atributu ihl povinné části IP hlavičky.

      ipRecv = (iphdr *)buffer;
      icmpRecv = (icmphdr *) (buffer + ipRecv->ihl * 4);

Kontrola příchozích dat

Jestliže jsou příchozí data menší, než by měly být (délka IP hlavičky + délka ICMP hlavičky) nebo se nejedná o ECHO odpověď na naši žádost, paket nás nezajímá.

      if (ipRecv->ihl * 4 + sizeof(icmphdr)
        > (unsigned int)lenght)
      {
    continue;
      }
      if ((icmpRecv->type == ICMP_ECHOREPLY)
        && (icmpRecv->code == 0)
        && (icmpRecv->un.echo.id == pid)
        && (icmpRecv->un.echo.sequence == 1))
      {
    cout << "Jedná se o odpověď na mou žádost"
        << endl;

Kontrola zpracování volitelné části IP hlavičky

Zkontrolujeme, jestli je volitelná část IP hlavičky skutečně typu „časové razítko“. Poté zjistíme, kolik přišlo vyplněných buněk. Jestliže jsme zaznamenávali kromě časů také IP adresy, je velikost jedné buňky 8 bytů, jinak 4 byty. Prvek s indexem IPOPT_OFFSET nyní udává posunutí na první prázdnou buňku. Odečteme-li od posunutí 4 (jsou to 4 byty údajů na začátku volby), získáme velikost všech buněk v bytech. Vydělíme-li tuto velikost velikostí jedné buňky (tu máme v proměnné fieldLength), získáme počet zaplněných buněk.

První 4 bity z čtvrtého bytu udávájí počet směrovačů, které nemohly zapsat své údaje, protože nebyl dostatek záznamů.

Pro lepší práci s bufferem přetypujeme ukazatel směřující na začátek první buňky na typ unsigned int *. Tím pádem budeme mít jedno nebo dvě čísla int na jednu buňku.

    if (ipOptions[IPOPT_OPTVAL] != IPOPT_TS)
    {
      cout << "Volitelná položka není \"TS\"
        (Time Stamp)" << endl;
      break;
    }
    cout << "Přišly "
    << (int)
      (ipOptionsRecv[IPOPT_OFFSET] - 4)/fieldLength
    << " vyplněné buňky." << endl;
    cout << "Buňky scházely pro "
    << (((int) ipOptionsRecv[IPOPT_OFFSET + 1]) >> 4)
    << " záznamů." << endl;
    time=(unsigned int *)&ipOptionsRecv[IPOPT_MINOFF];

Posutpně projdeme všechny buňky

    for (int i = IPOPT_MINOFF;
         i < ipOptionsRecv[IPOPT_OFFSET] - 1;
         i += fieldLength)
    {
      cout << endl;

Pouze časy

Každý prvek v poli time je jeden čtyřbyte. Zaznamenáváme-li pouze časy, má jedna buňka 4 byte. Proměnná i obsahuje počet bytů od začátku nepovinné volby (od začátku pole ipOptionsRecv), po odečtení úvodních 4 bytů získáme počet čtyřbytových buněk. Po vydělení čtyřmi máme index do poletime.

      if (onlyTime)
      {
        ms = ntohl(time[(i - 4) / 4]);
      }

Časy a IP adresy

Způsobem, který známe z minulých dílů, získáme IP adresu a přeložíme ji na doménové jméno.

      else
      {
        unsigned long int ip
            =*(unsigned long int*)&ipOptionsRecv[i];
        addrString=strdup(inet_ntoa(*(in_addr*)&ip));
        host=gethostbyaddr((char *)&ip, 4, AF_INET);
        cout << addrString
            << " ("
            << (host == NULL? "?" : host->h_name)
            << ")" << endl;
        free(addrString);
        ms = ntohl(time[(i - 4) / 4 + 1]);
      }

Zpracování časového razítka

První bit časového razítka udává, zda je časové razítko ve standardním tvaru, nebo není. Počet milisekund je obsažen v posledních 31 bitech. Proto zobrazuji hodnotu proměnné ms pomocí masky 0×7fffffff. Je-li první byte 1 (tedy posunu-li číslo o 31 bitů doprava a získám-li jedničku), není ve standardním tvaru. Nemá smysl se pokoušet jej převést na hodiny, minuty a sekundy. Jestliže je první byte 0, můžeme převádět čas pomocí funkce, kterou jsme vytvořili.

      cout << "Přišlo " << (ms & 0x7fffffff);
      if (ms >> 31)
      {
        cout << " - Není ve standardním tvaru."
            << endl;
      }
      else
      {
        getTime(ms, hour, min, sec);
        cout << " - " << hour << ":"
            << min << ":" << sec << endl;
      }

Konec programu

    }
    recv = true;
      }
    }
    else
    {
      cerr << "Nic nepřišlo" << endl;
      break;
    }
  } while (!recv);
  close(sock);
  return 0;
}

Funkce pro výpočet času

V programu používáme jednoduchou funkci, kterou postupným dělením z počtu milisekund získáme počet hodin, minut a sekund.

void getTime(unsigned int ms, unsigned int &h,
     unsigned int &m, unsigned int &s)
{
  ms /= 1000;
  s = ms % 60;
  ms /= 60;
  m = ms % 60;
  ms /= 60;
  h = ms;
}

Příklady ke stažení

Tabulka č. 491
Soubor Operační systém
lin28.tgz Linux
win28.zip MS Windows Ž

Oprava chyb

V minulém dílu se mi stala nepříjemná chyba. Když jsem vytvářel příklady, měl jsem několik verzí, které jsem postupně upravoval a došel tak k funkčnímu příkladu, který jsem později chtěl popsat v článku. Bohužel jsem ale nakonec nepracoval s úplně poslední funkční verzí. Výsledek je takový, že v článku je malá (ale poměrně zásadní) chyba a ukázkový příklad v MS Windows Ž nefunguje. V Linuxu funguje jen někdy. Za chybu se omlouvám.

Chyba spočívá v tom, že velikost volitelné části IP hlavičky v minulém dílu není 40 bytů, ale 39 bytů. Tři uvodní + 9 * 4 bytů. Je potřeba opravit dva řádky. Jednak zaplňování velikosti nepovinné části (řádek má vypadat ipOptions[IPOP­T_OLEN] = MAX_IPOPTLEN – 1;) a poté nastavování volby soketu. Řádek má vypadat if (setsockopt(sock, SOL_IP, IP_OPTIONS, ipOptions, MAX_IPOPTLEN – 1) == –1) . Dávám ke stažení příklady znovu, tentokrát opravené.

Tabulka č. 492
Soubor Operační systém
lin27.tgz Linux
win27.zip MS Windows Ž

Chyby jsem si všimnul až při psaní tohoto dílu, kdy jsem se hlouběji zanořil do podivně zmatené adresářové struktury na svém disku, ve které (jak se ukázalo při psaní minulého dílu) se bohužel nevyznám už ani já.

Školení: Úvod do XSLT 1.0

Akademie Root
  • Naučte se psát a udržovat transformace napsané v jazyce XSLT.
  • Lektorem kurzu je Jiří Kosek.
  • Osvojte si základní principy a konstrukce transformačního jazyka XSLT 1.0
  • XSLT se dnes používá zejména při prezentování XML v podobě webových stránek.

Detailní informace o kurzu...

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

Přehled názorů

netcraft
Tomas 15. 9. 2003 18:44
├ 
Re: netcraft
Taky Tomas 17. 9. 2003 09:49
├ 
Re: netcraft
Jiny Tomas 17. 9. 2003 09:51
└ 
Re: netcraft
neco tam napis 17. 9. 2003 10:01
Neco k prikladu
Radek 27. 9. 2003 21:55
       

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