Chceme-li posílat data pomocí raw soketu, musíme vyplnit odpovídající hlavičky protokolů. V minulém dílu jsme si vysvětlili jednotlivé položky v hlavičce IP protokolu a v hlavičce UDP protokolu. Nyní se konečně dostaneme k příkladu.
Volba IP_HDRINCL
Existuje volba soketu (viz článek
volby soketů), která se jmenuje IP_HDRINCL (IP Header Include). Je definována v úrovni voleb IPPROTO_IP. Je-li volba zapnutá (její hodnota je nastavena na true), musí být součástí dat, která odesíláme, také IP hlavička. Vložením IP hlavičky do odesílaného bufferu získáme možnost ovlivňovat atributy hlavičky IP protokolu. Druhá možnost by byla mít nastavenou volbu IP_HDRINCL na hodnotu false a ovlivňovat parametry IP protokolu pomocí funkce setsockopt
Důležité upozornění
Nyní jsme se dostali do oblastí, ve kterých se jednotlivé operační systémy od sebe dost liší. Nejprve je nutné upozornit na důležitý rozdíl mezi WinSoc a „klasickým“ socket API. Vytváříme-li soket typu SOCK_RAW s použitím pseudoprotokolu IPPROTO_RAW, je v Linuxu nastavena volba soketu IP_HDRINCL na hodnotu true. Naopak v MS WindowsŽ je nastavena na hodnotu false. Chcete-li pracovat v MS WindowsŽ se soketem tak, jak je uvedeno v článku, musíte hodnotu IP_HDRINCLnastavit na true. Na to NESMÍTE NIKDY zapomenout. Jinak se budete dost divit. (Viz příklady ke stažení.)
Ve skutečnosti nemůžu tvrdit, že by hlavička odchozího IP paketu byla stejná, jako je hlavička obsažená na začátku odesílacího bufferu (viz příklad). Operační systém může při odesílání bufferu nějakým způsobem do hlavičky zasáhnout. Kdybychom například dali jako identifikátor IP paketu číslo 0, jak MS WindowsŽ, tak i Linux by dodali správný identifikátor. Obecně zde ale nemohu moc říci. Liší se zde systém od systému (Linux, MS WindowsŽ, AIX atd…) a někdy se také liší i jednotlivé verze stejného systému. Chcete-li znát více podrobností ke konkrétnímu OS, musíte si prostudovat dokumentaci k podpoře IP protokolu daného OS.
V MS WindowsŽ jsme doposud používali knihovnu WinSock 1.1. Chceme-li pracovat se soketem typu SOCK_RAW s použitím pseudoprotokolu IPPROTO_RAW, musíme použít knihovnu WinSock 2.*. Liší se parametr funkce WSAStartup.
Pro vytvoření raw soketu musí program běžet s administrátorskými právy.
Příklad – UDP na Raw soketu
Formality
Nejprve provedeme potřebné formality. Vložíme hlavičkové soubory, začneme funkci main, deklarujeme proměnné a podobně. Při programování v MS Windows Ž nesmíme zapomenout na funkci WSAStartup.
#include <sys/socket.h>
#include <sys/types.h> #include <sys/ipc.h>
#include <netinet/in.h>
#include <netdb.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
hostent *host;
iphdr *ip;
udphdr *udp;
char *body = "Toto posílám přes RAW soket";
char *buffer, name[255];
int sock;
int total;
sockaddr_in serverSock;
short int port;
unsigned long int localIP;
gethostname(name, 255);
if (argc != 3)
{
cerr << "Syntaxe:\n\t" << argv[0]
<< " " << "adresa port" << endl;
return -1;
}
// Zjistíme info o lokálním počítači
if ((host = gethostbyname(name)) == NULL)
{
cerr << "Špatná adresa lokálního počítače" << endl;
return -1;
}
localIP = *((unsigned long int*)host->h_addr);
port = atoi(argv[2]);
// Zjistíme info o vzdáleném počítači
if ((host = gethostbyname(argv[1])) == NULL)
{
cerr << "Špatná adresa" << endl;
return -1;
}
Vytvoření soketu
Vytvoříme soket typu SOCK_RAW a použijeme pseudoprotokol IPPROTO_RAW.
// Vytvořím soket
if ((sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
{
cerr << "Nelze vytvořit soket" << endl;
return -1;
}
Alokujeme potřebnou paměť
Kromě samotných dat budeme také posílat záhlaví IP a UDP. Musíme spočítat jejich celkovou velikost a alokovat potřebný buffer. Také pro pohodlnější práci nastavíme a přetypujeme ukazatele na jednotlivé hlavičky.
total = sizeof(iphdr) + sizeof(udphdr) + strlen(body);
buffer = (char *)malloc(total);
ip = (iphdr *)buffer;
udp = (udphdr *)(buffer + sizeof(iphdr));
Zaplnění položek IP hlavičky
Postupně zaplníme položky IP hlavičky.
Verze IP
Používáme verzi číslo 4 protokolu IP.
ip->version = 4;
Velikost hlavičky
Nastavím velikost hlavičky vydělenou 4. Velikost hlavičky je 20 bytů.
ip->ihl = 5;
Typ služby
Typ služby se nepoužívá.
ip->tos = 0;
Velikost IP paketu
Nastavíme celkovou velikost paketu v bytech. Číslo převedeme do správného formátu pomocí funkce htons.
ip->tot_len = htons(total);
Identifikátor IP paketu
Identifikátor IP paketu je číslo, které jednoznačně identifikuje paket. Přiděluje jej OS. Já si zde číslo vymyslím. Tím se ale nejspíše chovám trochu nekorektně. Kdyby shodou náhod OS zrovna odesílal IP paket se stejným ID ke stejnému příjemci, mohl by nastat problém. Hlavně v případě, že by pakety byly po cestě fragmentovány. Fragmentované pakety se u příjemce skládají dohromady právě na základě identifikátoru IP. Pro jistotu dále zakážeme fragmentaci IP paketu nastavením bitu DF v příznacích.
ip->id = 12345;
Příznaky a offset fragmentu
První bit je roven 0. Druhý bit DF nastavíme na 1. Tím zakážeme fragmentaci. Snad by to nemělo vadit. Paket je dost malý. Třetí bit MF je roven 0. Hodnota 0 oznamuje, že paket je posledním fragmentem. Zbylých 13 bitů udává posunutí fragmentu od počátku originálního paketu. Zadáme 0. Je-li MF rovno 0 a offset roven 0, znamená to, že paket není fragmentován. Zadáme tedy hodnotu 16384, což je 0100000000000000 binárně. Opět použijeme funkci htons.
ip->frag_off = htons(16384); //0100000000000000 binárně
Doba životnosti paketu
Zde nastavíme počet počítačů (směrovačů), kterými může paket projít. Poté bude zahozen.
ip->ttl = 64;
Protokol vyšší vrstvy
Nastavíme protokol vyšší vrstvy. My budeme posílat UDP datagram. Zadáme hodnotu makra IPPROTO_UDP.
ip->protocol = IPPROTO_UDP;
Kontrolní součet – přednastavení
Kontrolní součet bude počítán z hlavičky IP datagramu, až budeme mít vyplněnou celou IP hlavičku. Jenomže kontrolní součet je součástí IP hlavičky. Musíme jej proto počítat z hlavičky, která má položku kontrolní součet nastavenou na 0.
ip->check = 0;
IP adresa odesílatele
Nastavíme IP adresu odesílatele, tedy našeho počítače.
ip->saddr = localIP;
IP adresa příjemce
Nastavíme IP adresu příjemce.
ip->daddr = *((unsigned long int*)host->h_addr);
Kontrolní součet – výpočet
Nyní pomocí funkce, kterou máme z RFC1071 dokumentu, vypočítáme kontrolní součet IP hlavičky. Prvním parametrem funkce je ukazatel na začátek IP hlavičky, druhým parametrem je velikost IP hlavičky. Mělo by se jednat o hodnotu atributu ihl * 4.
ip->check = htons(checksum((unsigned char *)ip, sizeof(iphdr)));
Zaplnění položek UDP hlavičky
Postupně zaplníme položky UDP hlavičky.
Číslo zdrojového portu
Nastavíme číslo UDP portu odesílatele. Pro převod do správného formátu použijeme funkci htons.
udp->source = htons(4999);
Číslo cílového UDP portu
Nastavíme číslo UDP portu příjemce. Pro převod do správného formátu použijeme funkci htons.
udp->dest = htons(port);
Velikost UDP datagramu
Nastavíme velikost UDP datagramu. Velikost UDP datagramu je součet velikostí UDP hlavičky a dat, která UDP datagram přenáší ve svém těle. Máme-li celkovou velikost IP paketu, můžeme také velikost UDP datagramu spočítat jako rozdíl velikosti IP paketu a IP hlavičky.
udp->len = htons(total - sizeof(iphdr));
Kontrolní součet UDP
Nyní bychom měli vypočítat kontrolní součet UDP. Nejprve bychom nastavili kontrolní součet na 0 a poté spočítali kontrolní součet z UDP datagramu. Kontrolní součet v UDP hlavičce se (na rozdíl od IP kontrolního součtu) počítá z celého UDP datagramu (hlavička + data). Podle mě ale na kontrolní součet UDP nikdo moc nehledí a nic se nestane, odešleme-li UDP datagram s položkou kontrolního součtu rovnou 0.
udp->check = 0;
Odeslání dat
Nejprve zaplníme strukturu sockaddr_in. Číslo portu ve struktuře není rozhodující. Důležité je číslo portu uvedené v UDP hlavičce. Dále vložíme přenášená data za UDP hlavičku a poté pomocí funkce sendto odešleme celý IP paket, který jsme vytvořili.
Sice by hodnota atributu sin_port neměla mít na nic vliv, ale ve WinSock přece jen vliv má. Musí být správně nastavená. Takže v programu pro MS WindowsŽ bude řádek serverSock.sin_port = htons(port);
serverSock.sin_family = AF_INET;
serverSock.sin_port = 0;
memcpy(&(serverSock.sin_addr), host->h_addr, host->h_length);
memcpy(((char *)udp) + sizeof(udphdr), body, strlen(body));
sendto(sock, buffer, total, 0, (sockaddr *)&serverSock, sizeof(sockaddr));
Konec
Pomocí typu soketu SOCK_RAW s použitím pseudoprotokolu IPPROTO_RAW nelze přijímat data. IP paket je na cestě a snad dojde i k příjemci. Nám nezbývá než skončit.
free(buffer);
close(sock);
return 0;
}
Funkčnost příkladu si můžete ověřit v kombinaci s UDP serverem z článku Protokol UDP 1. část. Podobným způsobem by šlo pracovat i s TCP protokolem. TCP protokol je ale trochu náročnější. Jednak má větší hlavičku, jednak bychom museli nejprve navázat spojení, posílat data, při tom pracovat s potvrzováním a nakonec spojení ukončit. Popsat veškeré techniky potvrzování a okna by zabralo mnoho článků a navíc bych si netroufal takové články psát.
Příklady
Popsaný program je k dispozici ke stažení pro Linux a MS Windows Ž.
Příklad pro MS WindowsŽ obsahuje hlavičkové soubory z minulého dílu.
Podívá-li se někdo na tento příklad a příklad UDP klienta z článku Protokol UDP 1. část, asi mu dnešní příklad připadá tak trochu jako úlet. Je to pravda. Pravděpodobně pro programování „normálních“ aplikací nebude nikdo posílat UDP datagramy pomocí syrového soketu. Tímto příkladem jsem chtěl pouze osvětlit celou problematiku internetového a transportního protokolu. Není ale pravda, že by syrové sokety neměly využití. Chceme-li pracovat s ICMP protokolem, nezbývá nám nic jiného než syrové sokety použít. ICMP protokolu se budeme věnovat v příštím článku.