Explicitní směrování je volitelná položka IP záhlaví, pomocí níž můžeme určit směrovače, kterými bude IP paket putovat. Již dříve jsme se setkali s buňkami na IP adresy ve volitelné části IP hlavičky. Například jsme v článku Sokety a C++ – zaznamenávání směrovačů obdrželi IP adresy směrovačů, kterými IP paket prošel. Odeslali jsme paket (IP paket nesoucí ICMP paket – žádost o ECHO), který měl buňky prázdné. V odpovědi byly buňky zaplněné.
Volba „explicitní směrování“ nám umožní zadat do každé buňky (maximálně jich bude 9) jednu IP adresu. IP paket od adresáta k příjemci poputuje právě přes počítače s danými IP adresami. A to i bez ohledu na fakt, že směrovače by, na základě hodnot ze směrovací tabulky, bez volby „explicitní směrování“ poslaly paket jinudy.
Volba „explicitní směrování“ nepřikazuje, aby IP paket šel pouze po zadaných IP adresách. Mezi určenými počítači může být libovolné množství jiných počítačů, přes které IP paket půjde.
Princip explicitního směrování
Jak se podaří „obejít“ nastavení směrovacích tabulek? Princip spočívá v tom, že IP paket vlastně nemá po cestě k cíli vyplněnou jako IP adresu cíl, ale postupně všechny IP adresy zadané ve volitelné části IP hlavičky typu „explicitní směrování“.
Představme si, že chceme IP paket poslat na počítač s IP adresou C a chceme, aby prošel počítači A a B. IP paket bude mít ve volitelné části IP hlavičky uloženy adresy A a B. Adresát bude C. V prvním kroku (ještě na počítači odesílatele – zajistí operační systém) se vymění adresa C za A a naopak. Tedy adresát bude A. Adresa C bude dočasně uchována ve volitelné části IP hlavičky. IP paket takto opustí počítač. Adresát IP paketu bude A. Tím zajistíme, že IP paket půjde na počítač A bez ohledu na to, že by třeba nebýt volby „explicitní směrování“ šel od odesílatele do C jinudy. IP paket dorazí do A, kde se opět přehodí A a C (IP paket se uvede do původního stavu) a poté se stejným způsobem přehodí B a C. Paket má tedy adresáta B, adresa C je dočasně umístěna ve volitelné části IP hlavičky. Díky tohoto nastavení IP paket putuje k B. A takhle se paket mění a postupně obchází všechny zadané IP adresy. Takhle lze „ošidit“ směrování. Volitelná část IP paketu obsahuje atribut „ukazatel“ (posunutí). Jedná se o třetí byte, který udává posunutí „aktuální“ buňky s IP adresou. Aktuální buňka je buňka, jejíž obsah bude při nejbližší příležitosti vyměněn za adresáta. Každý směrovač tedy kromě výměny adres, o které jsem se zmínil, také zvýší hodnotu ukazatele o 4. Díky toho každý směrovač pozná, se kterou adresou v seznamu má pracovat.
Formát „explicitní směrování“
Velikost | Název makra udávajícího posunutí | Popis |
---|---|---|
1 byte | IPOPT_OPTVAL | Udává typ volby. Volba „explicitní směrování“ má hodnotu 131 (makro IPOPT_LSRR). |
1 byte | IPOPT_OLEN | Udává velikost volitelné části IP hlavičky. |
1 byte | IPOPT_OFFSET | Ukazatel (posunutí) na aktuální buňku s IP adresou. Na začátku bude mít hodnotu 4. |
? (4 * počet buněk) | – | Jednotlivé čtyřbytové buňky s IP adresami. |
Pokud tabulku srovnáte s formátem položky zaznamenávej směrovače, zjistíte, že jsou úplně stejné. Liší se jen v hodnotách.
Selhání explicitního směrování
Je zřejmé, že explicitní směrování znamená určitý bezpečnostní problém. Směrovače většinou IP paket obsahující tuto volbu zahodí (z důvodu bezpečnosti). Jestliže dojde k zahození IP paketu, měl by zahazující směrovač informovat odesílatele IP paketem typu 3 (nedoručitelný IP paket), kódu 5 (explicitní směrování selhalo). Někdy ale směrovač zahazující paket neodešle ani signalizační ICMP paket odesílateli.
Velice špatně se bude hledat síť, kde IP paket s nastavenou položkou „explicitní směrování“ skutečně dojde k cíli (nebude zahozen). Dnes je zakazování explicitního směrování naprosto běžné. Ale kdo hledá, ten najde.
ICMP paket typu 3, kódu 5
Jedná se o ICMP paket signalizující selhání explicitního směrování. Jeho formát je v podstatě stejný jako formát ICMP paketu typu 3, kódu 3, o kterém jsme již mluvili. První čtyři byty jsou typ, kód a kontrolní součet. Další čtyři byty jsou nuly. Poté následuje IP hlavička zahozeného paketu a za ní teoreticky maximálně prvních 64 bytů dat z těla zahozeného IP paketu.
Příklad
Příklad opět odešle ICMP paket žádost o ECHO a bude očekávat ECHO odpověď. ICMP paket bude vložen do IP paketu s volitelnou položkou IP záhlaví typu explicitní směrování. Program bude také přijímat ICMP paket typu 3, kódu 5 (explicitní směrování selhalo). Program bude jako parametry příkazové řádky očekávat seznam IP adres.
Formality
Vložíme hlavičkové soubory, začneme funkcí main, deklarujeme proměnné, ošetříme parametry příkazové řádky, získáme informace z DNS o posledním parametru a připravíme strukturu sockaddr_in pro odeslání dat.
#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 <stdlib.h> #include <string.h> #define MAX 65536 using namespace std; unsigned short checksum(unsigned char *addr, int count); int main(int argc, char *argv[]) { socklen_t size; hostent *host = NULL; icmphdr icmp, *icmpRecv; iphdr *ipRecv; int sock, lenght; sockaddr_in sendSockAddr; fd_set mySet; timeval tv; unsigned short int pid = getpid(); char buffer[MAX]; char ipOptions[MAX_IPOPTLEN]; bool recv = false; if ((argc > 11) || (argc < 3)) { cerr << "Syntaxe:\n\t" << argv[0] << "seznam adres 2 - 10 adres" << endl; return -1; } if ((host = gethostbyname(argv[argc - 1])) == NULL) { cerr << "Spatna cílova adresa" << endl; return -1; } if ((sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) { cerr << "Nelze vytvorit soket" << endl; return -1; } sendSockAddr.sin_family = AF_INET; sendSockAddr.sin_port = 0; memcpy(&(sendSockAddr.sin_addr), host->h_addr, host->h_length);
Vyplnění ICMP hlavičky
Vytvoříme hlavičku ICMP žádosti o ECHO.
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 záhlaví
Typ položky
Nejprve buffer vynulujeme a poté nastavíme první byt na hodnotu 131 (makro IPOPT_LSRR).
memset(ipOptions, 0, MAX_IPOPTLEN); ipOptions[IPOPT_OPTVAL] = IPOPT_LSRR;
Velikost položky
Velikost položky jsou tři byty (úvodní část) + počet adres * 4 bytů. Počet adres je počet parametrů příkazové řádky – 1. Velikost se zapisuje na druhý byte.
ipOptions[IPOPT_OLEN] = 3 + (argc - 1) * 4;
Posunutí k první adrese
Nastavíme posunutí k první adrese. Posunutí se zapisuje na třetí byte. Bude mít hodnotu 4.
ipOptions[IPOPT_OFFSET] = IPOPT_MINOFF;
Vyplnění IP adres
Nyní postupně zjistíme informace z DNS o všech IP adresách, které byly předány jako parametr příkazové řádky. Budeme při tom měnit třetí byte našeho bufferu. Na závěr nesmíme zapomenout jej opět nastavit tak, aby ukazoval na první adresu.
for (int i = 1; i < argc; i++, ipOptions[IPOPT_OFFSET] += 4) { if ((host = gethostbyname(argv[i])) == NULL) { cerr << "Spatna mezi adresa" << endl; close(sock); return -1; } memcpy((void *)&ipOptions[ipOptions[IPOPT_OFFSET] - 1], (void *)host->h_addr, host->h_length); } ipOptions[IPOPT_OFFSET] = 4;
Nastavení volitelné části IP hlavičky.
Nastavíme náš buffer jako volitelnou část IP hlavičky.
if (setsockopt(sock, SOL_IP, IP_OPTIONS, ipOptions, ipOptions[IPOPT_OLEN]) == -1) { cout << "Nelze nastavit explicitni smerovani" << endl; close(sock); return -1; }
Odeslání dat
if ((sendto(sock, (char *)&icmp, sizeof(icmphdr), 0, (sockaddr *)&sendSockAddr, sizeof(sockaddr)) == -1)) { cerr << "Problem s odeslanim dat" << endl; close(sock); return -1; }
Příjem dat
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 << "Prijato " << lenght << " bytu ze " << inet_ntoa((in_addr)sendSockAddr.sin_addr) << endl;
Nastavení ukazatelů
Přečetli jsme příchozí data, která jsou uložena v bufferu jménem buffer. V datech je IP hlavička, která může obsahovat volitelné části. Za IP hlavičkou je ICMP hlavička.
ipRecv = (iphdr *)buffer; icmpRecv = (icmphdr *) (buffer + ipRecv->ihl * 4);
Kontrola velikosti bufferu
Nez začneme číst data, na která se odkazujeme přes nastavené ukazatele, měli bychom zkontrolovat, zda je buffer dostatečně velký – zda nebudeme něco číst z oblasti mimo příchozí buffer.
if (ipRecv->ihl * 4 + sizeof(icmphdr) > (unsigned int)lenght) { continue; }
Odpověď na naši žádost
Zkontrolujeme, zda se nejedná o ECHO odpověď na naši ECHO žádost.
if ((icmpRecv->type == ICMP_ECHOREPLY) && (icmpRecv->code == 0) && (icmpRecv->un.echo.id == pid) && (icmpRecv->un.echo.sequence == 1)) { cout << "Jedna se o odpoved" << " na mou zadost (neuveritelne)" << endl; recv = true; }
ICMP paket typu 3, kódu 5
Zkontrolujeme, jestli náhodou nejsme informováni o zahození našeho ICMP paketu z důvodu selhání explicitního směrování. O zahození paketu z důvodu selhání explicitního směrování jsme informováni ICMP paketem typu 3 (makro ICMP_DEST_UNREACH, kódu 5 (makro ICMP_SR_FAILED).
if ((icmpRecv->type == ICMP_DEST_UNREACH) && (icmpRecv->code == ICMP_SR_FAILED)) {
Kontrola těla ICMP paketu
ICMP paket typu 3, kódu 5 obsahuje v druhé polovině své hlavičky 4 byte nastavené na 0 a za ICMP hlavičkou následuje IP záhlaví + prvních maximálně 64 bytů zahozeného paketu. Nejprve zkontrolujeme, zda buffer je alespoň tak velký, abychom pomocí ukazatelů nečetli mimo přijatá data.
if ((unsigned int)lenght < ipRecv->ihl * 4 + 2 * sizeof(icmphdr) + sizeof(iphdr) + ipOptions[IPOPT_OLEN]) {
Nastavíme ukazatele na začátek zahozeného IP paketu. Hlavička zahozeného IP paketu začíná ihned za hlavičkou ICMP paketu typu 3, kódu 5. Za IP hlavičkou zahozeného paketu (může mít volitelné položky) by měla následovat námi vyplněná ICMP hlavička ECHO žádosti).
ipRecv = (iphdr *) ((char *)icmpRecv + sizeof(icmphdr)); icmpRecv = (icmphdr *) (((char *)ipRecv) + ipRecv->ihl * 4);
Zkontrolujeme, zda se opravdu jedná o naši ICMP žádost.
if ( (icmpRecv->type == ICMP_ECHO) && (icmpRecv->code == 0) && (icmpRecv->un.echo.id == pid) && (icmpRecv->un.echo.sequence == 1)) { cout << "Je to ICMP paket signalizujici" << " zahozeni naseho ECHO pozadavku" << " - explicitni smerovani selhalo" << endl; recv = true; } } }
Konec programu
Ukončíme všechny závorky, uzavřeme soket a ukončíme program.
else { cerr << "Nic neprislo" << endl; break; } } while (!recv); close(sock); return 0; }
Příklady ke stažení
Striktní směrování
Existuje ještě přísnější předpis na cestu, kterou má IP paket projít. Jedná se o volbu striktní směrování. Rozdíl je v tom, že mezi jednotlivými počítači, jejichž IP adresy zadáme do volitelné části IP hlavičky nesmí, být žádný další počítač. Princip je stejný jako u explicitního směrování (mám na mysli ony výměny IP adres), jen rozdíl je v tom, že u striktního směrování nesmí IP paket projít žádným jiným počítačem, než tím, který je právě nastaven v seznamu jako další. Takže počítač, jehož adresa je zadána jako další v pořadí, musí být vzdálen maximálně jeden „skok“ – musí být bezprostředním sousedem.
U striktního směrování je stejný formát jako u explicitního směrování. Jen typ není 131, ale 137 (makro IPOPT_SSRR).