Doposud jsme v rámci seriálu používali sokety pro přenos dat způsobem, který po nás nevyžadoval cokoliv vědět o principech počítačových sítí. Stačilo znát pár funkcí, umět vybrat vhodný protokol (TCP nebo UDP), a bylo možné posílat a přijímat data. Dnes se podíváme trochu „pod pokličku“.
Vrstvy komunikačního modelu TCP/IP
Aplikační vrstva |
Transportní vrstva |
Internetová vrstva |
Síťová |
Vrstvy komunikačního modelu ISO OSI
Aplikační vrstva |
Prezentační vrstva |
Relační vrstva |
Transportní vrstva |
Síťová vrstva |
Linková vrstva |
Fyzická vrstva |
Model ISO OSI je dán standardizační komisí a jedná se vlastně o standard. Malý problém je ale v tom, že internet (založený na modelu TCP/IP) vznikl trochu dříve než norma ISO OSI. V podstatě to ale nevadí, oba modely se totiž nevylučují. Ve skutečnosti spolu splývají. ISO OSI vlastně jen upřesňuje TCP/IP. Fyzická a linková vrstva ISO OSI v podstatě odpovídají síťové vrstvě modelu TCP/IP. Síťová vrstva ISO OSI odpovídá internetové vrstvě modelu TCP/IP. Transportní vrstvy jsou stejné a vrchní tři vrstvy ISO OSI odpovídají aplikační vrstvě modelu TCP/IP.
Nebudu se rozepisovat, jaké jsou úlohy a role jednotlivých vrstev. Tato problematika je podrobně vysvětlena v úvodu snad každé knihy o počítačových sítí. Snad jen je dobré uvést příklady protokolů. V modelu TCP/IP:
- Aplikační – HTTP, FTP a podobně
- Transportní – TCP, UDP
- Internetový – IP
- Síťový – například Ethernet a jiné
Když jsme v úvodu seriálu vytvářeli TCP klienta a TCP server, aplikační protokol byl vlastně definován velice jednoduše. Na textový řetězec „Ahoj“ odpovídal server „Nazdar“. Používat pro něco takového pojem „protokol aplikační vrstvy“ je asi trochu silné slovo. My si ale na tomto příkladě ukážeme, v jaké formě data putovala po síti.
Přenos dat v modelu TCP/IP
Data „vzniknou“ na nejvyšší vrstvě, tedy aplikační. V našem případě se jedná o textový řetězec „Ahoj“. Tedy krátkou posloupnost bytů. Ta je předána protokolu transportní vrstvy, v našem případě se jednalo o TCP. Žádná vrstva se nezabývá obsahem dat, které jí jsou předána z vrstvy vyšší. V transportní vrstvě je vytvořená odpovídající hlavička TCP protokolu, za kterou jsou vložená data, která byla předána z vyšší vrstvy. Tím došlo k vytvoření TCP segmentu (hlavička TCP a data z vyšší vrstvy). TCP segment je předán nižší (internetové) vrstvě. Zde se na TCP segment pohlíží jako na data. Je vytvořena hlavička IP protokolu a za ní je přidán TCP segment. Celému bloku dat se říká IP paket. Tato data jsou předána síťové vrstvě. Například na síťové vrstvě je používán Ethernet, potom bude vytvořen ethernetový rámec. Budou vytvořena ethernetové záhlaví a ethernetové zápatí a mezi ně bude vložen IP paket. A data mohou vyrazit na cestu. U příjemce jdou data od nejnižší vrstvy nahoru a na každé vrstvě jim je „odseknuta“ hlavička. Až nakonec aplikační vrstva obdrží řetězec „Ahoj“.
Samozřejmě, že celá problematika je trochu složitější, například mohou vzniknout situace, že data předávána vyšší vrstvou nižší se nemusejí vejít do bloku dat nižší vrstvy. Také IP paket se může po cestě při průchodu různými lokálními sítěmi „obalovat“ do různých síťových protokolů. A podobně. Pro představu to ale stačí.
Záhlaví ethernetového rámce | Hlavička IP paketu | Hlavička TCP segmentu | Přenášená data (Řetězec „Ahoj“) | Zápatí ethernetového rámce |
Jak je tedy vidět, použití soketů typu SOCK_STREAM nebo SOCK_DGRAM před námi skrylo mnoho podrobností. Existuje ale typ soketu, jmenuje se SOCK_RAW, který nám umožní (kromě síťové vrstvy) mít plně pod kontrolou tvorbu všech hlaviček. Slovo „raw“ lze přeložit jako syrový nebo hrubý. My si příště předvedeme syrový soket na příkladu, kde pomocí něj odešleme UDP datagram. Jako název protokolu použijeme IPPROTO_RAW – 3. parametr funkce socket. Nejedná se vlastně o protokol ve smyslu třeba TCP nebo UDP. Hodnota makraIPPROTO_RAW sděluje operačnímu systému, že chceme vyplňovat hlavičku IP a hlavičku protokolu transportní vrstvy (v našem případě se bude jednat o UDP).
Se síťovou vrstvou pomocí soketů pracovat nelze. Takže kombinací maker SOCK_RAW jakožto typu soketu a IPPROTO_RAW jakožto názvu protokolu se vlastně dostaneme „nejníže“, jak jen to v soketech jde.
Než začneme s psaním samotného příkladu (v příštím dílu), podíváme se na význam jednotlivých položek v hlavičkách protokolů IP a UDP.
Malá poznámka k WinSock
Celý seriál používám (příklady píšu) pro prostředí DEV-CPP založené na překladači MinGW. Vůbec to nedokážu pochopit (nebo to možná jen nedokážu najít :-) ), ale MinGW nemá v hlavičkových souborech, které jsou s ním dodávány, deklarovány struktury potřebné pro práci s IP, TCP a UDP záhlavím. Také tam nejsou deklarována některá makra. Je vidět, že MinGw má hodně co dohánět. Aby šel níže napsaný příklad použít i v DEV-CPP, vytvořil jsem potřebné struktury, které jsou na konci článku ke stažení. 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.
Hlavička IP protokolu
Velikost (bity) | Název atributu | Název položky | Popis |
---|---|---|---|
4 | version | Verze IP | Verze IP protokolu. Budeme zadávat vždy 4 – časem možná i 6 :-). Ale hlavička IP verze 6 vypadá jinak než hlavička IP verze 4. Atribut verze společně s následujícím atributem tvoří první byte hlavičky IP. |
4 | ihl | Délka záhlaví (počet 4 bytů záhlaví) | Velikost záhlaví / 4. Záhlaví IP může obsahovat nepovinné položky. Jeho velikost proto není předem dána. Jestliže nebude velikost IP záhlaví dělitelná čtyřmi, musí být IP hlavička doplněna nulami. Povinné položky IP hlavičky mají celkem 20 bytů. Proto je nejmenší možná hodnota 5. My nebudeme používat nepovinné položky IP hlavičky. Společně s předchozím atributem tvoří první byte hlavičky IP. |
8 | tos | Typ služby | Typ služby. Nepoužívá se. Budeme nastavovat na 0. |
16 | tot_len | Celková velikost IP paketu | Velikost IP hlavičky a dat IP paketu |
16 | id | Identifikátor datagramu | Jednoznačný identifikátor datagramu. Číslo přiděluje OS. Jestliže se IP paket nevejde do síťového rámce, bude rozdělen na menší IP pakety – fragmentace. Každý fragment je platným IP paketem. Všechny fragmenty, na které je IP paket rozdělen, mají stejné identifikační číslo. Na základě identifikačního čísla jsou u příjemce IP fragmenty spojovány v původní celek. |
3 | frag_off | Příznaky | 3 bity příznaků IP paketu.
|
13 | frag_off | Offset fragmentu | Posunutí (offset) fragmentu v původním IP paketu. |
8 | ttl | Doba životnosti paketu (Time To Live) | Číslo udávající, kolika počítači (směrovači) ještě může paket projít, než bude zahozen. O TTL jsme si povídali v minulém dílu. |
8 | protocol | Protokol vyšší vrstvy | Protokol transportní vrstvy. Udává, jaká data (jaký typ dat) paket přenáší. |
16 | check | Kontrolní součet | Kontrolní součet vypočítaný POUZE z hlavičky IP paketu. Funkci pro výpočet kontrolního součtu si „půjčíme“ z RFC1071 dokumentu. |
32 | saddr | IP adresa odesílatele. | |
32 | daddr | IP adresa příjemce. |
Struktura popisující hlavičku IP protokolu (pouze povinné části) se jmenuje iphdr. Je deklarována v hlavičkovém souboru netinet/ip.h.
Hlavička UDP protokolu
Velikost (bity) | Název atributu | Název položky | Popis |
---|---|---|---|
16 | source | Zdrojový port | Číslo zdrojového UDP portu – UDP port odesílatele. |
16 | dest | Cílový port | Číslo UDP portu příjemce |
16 | len | Velikost UDP datagramu | Velikost (hlavička + data) |
16 | check | Kontrolní součet | Kontrolní součet je (měl by být) počítán z hlavičky i z dat. My ale bude dosazovat 0. Podle mě se nic nestane. |
Název struktury s UDP hlavičkou je udphdr. Je deklarována v hlavičkovém souboru netinet/udp.h
Raw soket
V příštím dílu jako ukázku vytvoříme raw soket, pomocí kterého odešleme UDP datagram.
sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);
V kombinaci parametrů SOCK_RAW a IPPROTO_RAW budeme muset vyplnit hlavičku IP paketu i UDP datagramu. V dalších dílech potom budeme pracovat s ICMP protokolem. Jako poslední parametr funkce socket budeme dosazovat IPPROTO_ICMP. V takovém případě nám OS sám vyplní hlavičku IP datagramu.
Potřebné deklarace pro MinGW – budeme je potřebovat v příštím dílu.