Programowanie
Programowanie
Programowanie przy użyciu gniazd sieciowych
Programowanie przy użyciu gniazd sieciowych
Programowanie
gniazd sieciowych
Rafał Kułaga
Podstawową umiejętnością, którą musi opanować każdy programista chcący pisać aplikacje sieciowe,
jest wykorzystanie mechanizmu gniazd sieciowych (ang. network sockets). Pozwala on na wygodne
przesyłanie i odbieranie danych, niezależnie od wykorzystywanego sprzętu sieciowego. Podstawową
ideą gniazd sieciowych jest bowiem zapewnienie warstwy abstrakcji dla niskopoziomowych funkcji
sieciowych. Jeżeli chcesz dowiedzieć się, w jaki sposób nowoczesne systemy operacyjne realizują
komunikację sieciową, jakie są rodzaje gniazd sieciowych oraz w jaki sposób możesz wykorzystać je
w swoich aplikacjach, to jest to artykuł dla Ciebie. Zapraszam do lektury!
.pl
Jestem przekonany, że nikogo nie trzeba prze-li pełną kontrolę nad reprezentacją danych wysyłanych
konywać co do znaczenia funkcji sieciowych poprzez sieć.
software.com
we współczesnych aplikacjach. Śmiało można
Jako programista aplikacji sieciowych korzystających
stwierdzić, że zdecydowana większość dostęp- jedynie z mechanizmu gniazd, udostępnianego przez sys-
linux@
nych na rynku programów (w tym również gier kompu- tem operacyjny, będziesz odpowiedzialny za zdefiniowa-
terowych), w taki czy inny sposób wykorzystuje połącze- nie protokołu transmisji danych pomiędzy dwoma proce-
nia sieciowe komputera. Dotyczy to nie tylko baz danych, sami, działającymi na oddalonych maszynach. Może się aplikacji biznesowych i wspomagających zarządzanie, w to wydawać dość skomplikowane, szczególnie jeżeli nie
których naturalne jest zastosowanie architektury klient- posiadasz zbyt dużej wiedzy o budowie typowych proto-serwer, lecz również całej gamy aplikacji działających w kołów sieciowych warstwy aplikacji, jednak w rzeczywi-architekturze równy z równym (ang. P2P – Peer To Peer), stości sprowadza się do odpowiedniego przemyślenia roz-pozwalających na wymianę plików.
kładu danych w części pakietu przetwarzanej przez apli-
Kiedy mówimy o programowaniu sieciowym, z kację.
pewnością przychodzą nam na myśl języki programo-
Tworzenie bezpiecznych aplikacji w języku C/C++
wania, takie jak PHP, ASP, J2EE. Rozwiązania budo- korzystających z gniazd sieciowych wymaga od nas jed-
wane przy ich użyciu niemal zawsze korzystają z funk- nak dużej ostrożności w manipulowaniu otrzymany-
cji sieciowych. Ich cechą charakterystyczną jest wyko- mi danymi. Pamiętajmy, że błędy w tym aspekcie mo-
rzystanie strony internetowej jako interfejsu użytkow- gą spowodować podatność naszego programu na ataki nika oraz przesyłanie poleceń i efektów ich wykona- wykorzystujące przepełnienie bufora (ang. buffer over-nia za pomocą protokołu HTTP. W tym artykule skupi- flow). Na szczególne niebezpieczeństwo zostaje narażo-my się jednak na innym aspekcie programowania przy ny nasz system w przypadku, gdy program uruchamia-
wykorzystaniu sieci komputerowych – będziemy mie- ny jest z uprawnieniami użytkownika root, co często jest
64
październik 2009
www.lpmagazine.org
65
Programowanie
Programowanie
Programowanie przy użyciu gniazd sieciowych
Programowanie przy użyciu gniazd sieciowych
konieczne w celu wykorzystania niższych nu-
merów portów.
Listing 1. Podstawowe struktury
W artykule zostanie opisany sposób dzia-
struct addrinfo
łania gniazd sieciowych, ich typy oraz zasto-
{
sowanie. W trakcie lektury artykułu nauczysz int ai_flags; // flagi sterujące
się, jak tworzyć proste aplikacje posiadające int ai_family; // protokół: AF_INET (IPv4), AF_INET6
możliwość bezpiecznej wymiany danych za (IPv6), AF_UNSPEC (dowolny)
pomocą sieci komputerowej. Przyjrzymy się int ai_socktype; // typ gniazda: SOCK_STREAM (TCP), również narzędziom, pozwalającym na testo-SOCK_DGRAM (UDP)
wanie aplikacji korzystających z mechanizmu int ai_protocol; // protokół
gniazd. Wspomnimy również o dwóch bar-
size_t ai_addrlen; // rozmiar struktury ai_addr
dzo przydatnych bibliotekach – libnet i libp-
struct sockaddr* ai_addr; // wskaźnik na strukturę sockaddr_in
cap, pozwalających na niskopoziomowy do-
char* ai_canonname; // nazwa hosta
stęp do sieci.
struct addrinfo* ai_next; // następny element listy
};
System operacyjny
a komunikacja sieciowa
struct sockaddr
Zanim przejdziemy do praktycznej realiza-
{
cji komunikacji sieciowej za pomocą mecha-
unsigned short sa_family; //wersja adresu: AF_INET (Ipv4),
nizmu gniazd, warto poznać sposób, w ja-
AF_INET6 (Ipv6)
ki system operacyjny (a konkretnie część ją-
char sa_data[14]; //tablica przechowująca adres
dra systemu, zwana stosem TCP/IP) obsługu-
};
je te funkcje.
Gdy nasz komputer odbiera sygnały po-
struct sockaddr_in
chodzące z sieci, sprzęt, a konkretnie – inter-
{
fejs sieciowy, usuwa nagłówki protokołów short int sin_family; //rodzina adresów: AF_INET
warstw znajdujących się poniżej warstwy sie-
unsigned short int sin_port; //numer portu
ciowej. Od tej chwili, za obróbkę odebranych struct in_addr sin_addr; //struktura przechowująca adres IP
danych odpowiada jedynie system operacyjny unsigned char sin_zero[8]; //wypełnić zerami oraz aplikacje (Rysunek 1).
};
W czasie przetwarzania przez część stosu
TCP/IP odpowiedzialną za obsługę protoko-
struct in_addr
łów warstwy sieciowej, obcięty zostaje nagłó-
{
wek protokołu IP. Otrzymany segment (jed-
uint32_t s_addr; //adres IP
nostka danych protokołów warstwy transpor-
};
tu) zostaje przekazany w górę stosu.
Na poziomie warstwy transportu mamy
do czynienia z numerami portów, stanowią-
cych identyfikatory, pozwalające na przeka-
zanie danych wydobytych z segmentu do od-
żżżżżżżżżżżż
powiedniego procesu. W większości syste-
mów operacyjnych, obsługiwane są dwa ty-
żżżżżżżżżżżżżż
py portów, odpowiadające dwóm najpopu-
larniejszym protokołom warstwy transportu:
TCP i UDP. Dla każdego z protokołów przy-
dzielony jest zakres portów – zwróćmy jed-
żżżżżżżż
nak uwagę, że różne procesy mogą korzy-
żżż
żżżżżżżż
stać z tego samego numeru portu pod warun-
kiem, że używają różnych protokołów war-
stwy transportu.
Każdy z portów posiada unikalny numer
żżżżżżżż
żż
żżżżżżż
identyfikujący z zakresu 0 – 65535 (port iden-
tyfikowany jest za pomocą 16-bitowej liczby
naturalnej). Porty o numerach 0 – 1023 okre-
ślane są jako ogólnie znane i przypisane do
żżżżżżżż
najpopularniejszych usług (takich jak telnet,
żżżżż
żżżżżżżżżż
żżżżżż
żżżżż
WWW, e-mail). W celu utworzenia gniaz-
da i przypisania go do portu z tego zakresu,
konieczne są uprawnienia użytkownika root. Rysunek 1. Enkapsulacja danych w modelu TCP/IP
64
październik 2009
www.lpmagazine.org
65
Programowanie
Programowanie
Programowanie przy użyciu gniazd sieciowych
Programowanie przy użyciu gniazd sieciowych
Porty o wyższych numerach możemy dowol- tor, przekazywany w wywołaniach systemo- ciowych znajdziesz na stronach wymienio-nie przypisywać tworzonym przez nas apli- wych, informujący jądro, na jakim obiekcie nych w tabelce W Sieci).
kacjom.
ma zostać wykonana dana operacja. Gniazda
W obrębie gniazd internetowych wyróż-
Jednak w jaki sposób system operacyjny sieciowe są kolejnym mechanizmem, do któ- niamy trzy najważniejsze typy: wie, jaki port przypisany jest do danej aplika- rego dostęp odbywa się za pomocą deskryp-cji, oraz w jaki sposób możemy dokonać takie- torów plików.
ó Gniazda połączeniowe TCP – transmisja
go przypisania? Właśnie w tym celu stosuje się
danych realizowana przy użyciu gniazd
gniazda sieciowe.
Typy gniazd sieciowych
tego typu odbywa się z wykorzystaniem
Istnieje wiele różnych standardów siecio-
protokołu TCP w warstwie transportu.
Gniazda sieciowe
wych, jak również wiele protokołów warstwy
Gwarantuje on dostarczenie danych, za-
Z pewnością wiesz, że w systemach unikso- transportu. Z tego względu mamy do czynie-
pobiega odebraniu pakietów w nieodpo-
wych dostęp do urządzeń, plików, katalogów nia z wieloma typami gniazd. W artykule opi-
wiedniej kolejności, kosztem prędkości
oraz kolejek FIFO odbywa się za pomocą de- sane zostały jedynie standardowe gniazda in-
przesyłania danych;
skryptorów plików. Są one liczbami całkowi- ternetowe, służące do komunikacji za pomo- ó Gniazda bezpołączeniowe UDP – trans-tymi, zapisanymi w postaci typu int języka cą sieci lokalnych oraz internetu (więcej in-
misja danych realizowana przy użyciu
C/C++. Deskryptor pliku stanowi identyfika- formacji na temat innych typów gniazd sie-
gniazd tego typu odbywa się z wyko-
rzystaniem protokołu UDP w warstwie
Listing 2. Sposób użycia funkcji getaddrinfo()
transportu. Protokół UDP jest protoko-
#include
//definicje typów danych
łem bezpołączeniowym – oznacza to,
#include //obsługa gniazd
że nie jest gwarantowane dostarczenie
#include //operacje na sieciowej bazie danych
pakietów, ani ich odpowiednia kolej-
ność. Protokół UDP znajduje zastoso-
int getaddrinfo(const char* node, // adres IP lub nazwa domenowa
wanie tam, gdzie ważna jest duża szyb-
const char* service, // port lub nazwa usługi
kość przesyłania danych, a utrata czę-
const struct addrinfo* hints, // struktura służąca jako wzór
ści pakietów nie stanowi dużego pro-
struct addrinfo** res); // wskaźnik na początek listy wyników
blemu (media strumieniowe, gry kom-
puterowe);
// w kodzie programu
ó Gniazda r aw (ang. raw sockets)
int errn; // kod błędu
– gniazda sieciowe, które dają aplika-
struct addrinfo hints; // wzór dla wywołania getaddrinfo()
cjom bezpośredni dostęp do nagłów-
struct addrinfo* nodeinf; // informacje o interesującym nas adresie
ków pakietu. Gdy korzystamy z gniazd
raw, jesteśmy odpowiedzialni za odpo-
memset(&hints, 0, sizeof(hints)); // czyścimy strukturę
wiednie przypisanie wartości wszyst-
hints.ai_family = AF_INET; // interesuje nas jedynie protokół Ipv4
kim polom. Podczas gdy w codzien-
hints.ai_socktype = SOCK_STREAM; // będziemy używać protokołu TCP
nym zastosowaniu byłoby to co naj-
hints.ai_flags = AI_PASSIVE; // tylko, jeżeli chcemy pobrać nasz adres!
mniej niewygodne, to w pewnych
przypadkach (takich jak np. testowa-
if ((errn = getaddrinfo(NULL, ”5000”, &hints, &nodeinf)) == -1)
nie firewalli oraz oprogramowania sie-
{
ciowego pod kątem bezpieczeństwa,
fprintf(stderr, śgetaddrinfo error: %s\n", gai_strerror(errn));
szczególnie pod względem odporności
exit(1);
na ataki Denial of Service) jest to nie-
zwykle przydatna możliwość;
W niniejszym artykule opiszemy zastosowa-
nie dwóch pierwszych typów gniazd siecio-
wych. Jeżeli jesteś zainteresowany bliższym
poznaniem tematyki związanej z gniazdami
raw, to polecam zapoznanie się z biblioteka-
mi libpcap i libnet, o których powiemy w dal-
szej części artykułu.
Działanie gniazd sieciowych
W celu utworzenia nowego gniazda, korzysta-
my z wywołania systemowego socket(), po-
dając typ gniazda jako argument. Zwraca ono
wartość typu int, będącą deskryptorem pli-
ku. Po utworzeniu, gniazdo nie jest przypisa-
ne do żadnego portu – jeżeli chcemy tego do-
Rysunek 2. Interfejs programu Wireshark
konać, korzystamy z wywołania systemowego
66
październik 2009
www.lpmagazine.org
67
Programowanie
Programowanie
Programowanie przy użyciu gniazd sieciowych
Programowanie przy użyciu gniazd sieciowych
bind(), jako argument podając deskryptor pli- (szczególnie w przypadku większych aplika-
Powiedzieliśmy już, że wykorzystując
ku gniazda oraz żądany port.
cji), jest utworzenie odpowiednich obiektów, mechanizm gniazd sieciowych, jesteśmy od-
Od tej chwili możemy na danym porcie reprezentujących wykorzystywane gniazda i powiedzialni za zdefiniowanie zasad, na któ-
nasłuchiwać połączeń (za pomocą wywołania ukrywające przed nami szczegóły działania rych mają porozumiewać się ze sobą progra-systemowego listen()) oraz akceptować je konkretnych wywołań systemowych. Roz- my. W tym przypadku, wykorzystanie snif-przy użyciu funkcji accept(). Zaakceptowa- wiązanie takie jest szczególnie warte polece- ferów w celu rozwiązywania problemów nie połączenia powoduje utworzenie nowego nia, jeżeli swój kod zamierzasz wykorzysty- okazuje się wręcz niezbędne – pozwala bo-deskryptora pliku, pozwalającego na oddziel- wać wielokrotnie, w różnych aplikacjach.
wiem na wizualizację przesyłanych danych
ną obsługę komunikacji z każdym z łączących
oraz szybkie wykrywanie błędów w ich re-
się komputerów.
Testowanie programów
prezentacji.
Wysyłanie i odbieranie danych odbywa wykorzystujących gniazda
się przy pomocy funkcji send() i recv(). Na- Wykorzystując w praktyce informacje zawar- Tcpdump i tcpflow
leży tu pamiętać o istnieniu maksymalnej jed- te w tym artykule, z pewnością nie raz natra- Większość Czytelników z pewnością miała nostki transmisyjnej (MTU – ang. Maximum fisz na problemy i trudne do wykrycia błędy w już kiedyś doświadczenia z tymi narzędziami Transmission Unit), określającej maksymal- kodzie, uniemożliwiające poprawną wymia- – pozwalają one na monitorowanie danych od-ny rozmiar datagramu. Zawsze należy spraw- nę danych. W takim przypadku, oprócz stan- bieranych i wysyłanych przez nasz komputer dzać, czy rzeczywisty rozmiar przesłanych da- dardowego debuggera, warto mieć również za pośrednictwem sieci.
nych (zwracany przez funkcję send()) pokry- pod ręką programy, które pozwolą nam prze-
Tcpdump jest standardowo dostępny w
wa się z rozmiarem żądanym – jeżeli jest ina- konać się, jakie dane w rzeczywistości wysy- każdej dystrybucji Linuksa. W celu przechwy-czej, musimy wywołać funkcję send() jesz- łamy w sieć.
tywania danych korzysta z niskopoziomowych
cze raz, tym razem odpowiednio modyfikując
wskaźnik początku obszaru w pamięci.
Listing 3. Sposób użycia wywołania socket()
Znacznie prościej wygląda obsługa komu-
#include
nikacji przy pomocy gniazd bezpołączenio-
#include
wych UDP – przesyłać i odbierać dane mo-
żemy bezpośrednio po otrzymaniu deskrypto-
int socket(int domain, // wersja protokołu IP: PF_INET (v4) lub
ra pliku gniazda. Wykorzystujemy w tym celu PF_INET6 (v6)
dwie funkcje: sendto() i recvfrom(). War-
int type, // typ gniazda: SOCK_STREAM, SOCK_DGRAM
to również wspomnieć o możliwości połącze-
int protocol); // 0, jeżeli chcemy by protokół został wybrany
nia gniazd UDP – w takim przypadku możemy za nas
korzystać ze standardowych funkcji send() i
recv(). Pamiętaj jednak, że dane nadal prze-
syłane będą przy użyciu protokołu UDP – ich int sock;
dotarcie do celu nie będzie gwarantowane.
// tu wywołujemy funkcję getaddrinfo() jak w Listingu 2
Po zakończeniu przesyłania danych, na-
sock = socket(nodeinf->ai_family, nodeinf->ai_socktype, nodeinf->ai_
leży zamknąć deskryptor pliku gniazda przy protocol);
użyciu wywołania systemowego close(). Je-
żeli chcemy poprawnie zakończyć połączenia Listing 4. Sposób użycia funkcji bind()
dla gniazd TCP, to powinniśmy przed tym wy-
#include
wołać funkcję shutdown().
#include
O aktualnie otwartych gniazdach oraz sta-
nie, w jakim się znajdują, możemy dowiedzieć int bind(int sockfd, // deskryptor pliku gniazda się przy użyciu programu netstat. Dokładne struct sockaddr* my_addr, // adres gniazda (IP i port) naszego komputera informacje na temat jego użycia znajdziesz w int addrlen); // wielkość struktury my_addr
dokumentacji (man netstat).
// wywołujemy funkcje getaddrinfo() i socket() tak jak w Listingu 2 i
Wymagane
Listingu 3
biblioteki i pliki nagłówkowe
if(bind(sockfd, nodeinf->ai_addr, nodeinf->ai_addrlen) == -1)
Do rozpoczęcia programowania przy użyciu {
gniazd sieciowych wystarczy nam dowolna perror(NULL);
dystrybucja Linuksa z zainstalowanymi pa-
exit(1);
kietami klasy Development. W artykule nie }
wykorzystujemy żadnych dodatkowych bi-
bliotek, jedynie standardowe wywołania sys-
Listing 5. Sposób użycia funkcji listen()
temowe.
#include
Do kompilacji polecam zastosować kom-
pilator gcc w przypadku gdy programy pisane int listen(int sockfd, // deskryptor pliku gniazda są w języku C i g++ dla języka C++. Zwróć int backlog); // dozwolona liczba połączeń w kolejce uwagę, że bardzo wygodnym rozwiązaniem
66
październik 2009
www.lpmagazine.org
67
Programowanie
Programowanie
Programowanie przy użyciu gniazd sieciowych
Programowanie przy użyciu gniazd sieciowych
mechanizmów sieciowych systemu, do których Wireshark
Podstawowe struktury
dostęp wymaga uprawnień użytkownika root. Przyznam, że opisując powyższe programy, Wiesz już, jak działa mechanizm gniazd Aby rozpocząć przechwytywanie na interesują- nie mogłem się doczekać, kiedy przejdziemy sieciowych w Linuksie – przyszedł czas cym nas interfejsie, z zapisem do pliku, należy do aplikacji Wireshark (Rysunek 2). Jest to na praktyczne wykorzystanie go w tworzo-wydać polecenie:
bowiem zdecydowanie najlepszy program do nych aplikacjach. Zanim jednak zajmiemy
analizy przechwyconego ruchu sieciowego, się opisem poszczególnych wywołań sys-
tcpdump -i interfejs -w nazwa_
oferujący wiele bardzo przydatnych i łatwych temowych, opiszemy podstawowe struktu-
pliku.dmp filtry
w obsłudze funkcji.
ry, których poznanie jest niezbędne w ce-
Od razu chciałbym Cię jednak prze- lu efektywnego wykorzystania informacji
Filtry to wyrażenia, informujące program tcp- strzec przed uruchamianiem programu Wi- zawartych w dalszej części artykułu. Oma-dump, jakie pakiety są dla nas interesujące z reshark z uprawnieniami użytkownika ro- wiane struktury przedstawione zostały na punktu widzenia dalszej analizy. Dokładny ot. Może to być bardzo niebezpieczne, Listingu 1.
opis działania wszystkich opcji programu oraz szczególnie jeżeli korzystasz z dodatko-
filtrów znajdziesz w dokumentacji aplikacji wych parserów protokołów. Zdecydowanie addrinfo – informacje o adresach (man tcpdump).
lepszym rozwiązaniem jest przechwycenie Podstawową strukturą jest addrinfo, słu-
Program tcpflow różni się od programu tcp- ruchu do pliku (przy użyciu programu tcp- żąca do przechowywania informacji o adre-dump przeznaczeniem – za jego pomocą może- dump) a następnie jego analiza w pakiecie sach. Zawiera informacje o wersji protoko-my przekonać się o postaci danych przesyłanych Wireshark.
łu IP, typie gniazda, protokole, adres gniazda
za pośrednictwem strumieni TCP, przez co może
Najnowszą wersję programu Wi- (struktura sockaddr) Najważniejszą funkcją,
okazać się wygodniejszym narzędziem do testo- reshark znajdziesz na stronie http: operującą na strukturze addrinfo, jest ge-wania tworzonych programów. Aplikacja ta nie //www.wireshark.org/ oraz w repozytoriach taddrinfo(). Dzięki niej uzyskujemy listę jest jednak standardowo dostępna w większości większości dystrybucji. Tą drugą opcję po- wszystkich adresów danego hosta, spośród dystrybucji – możesz ją pobrać ze strony ht p: lecam szczególnie tym, którzy chcieliby których możemy wybrać najbardziej nam od-
/ www.circlemud.org/~jelson/software/tcpflow/. uniknąć dość czasochłonnej kompilacji i powiadający.
Znajdują się tam również pakiety binarne dla rozwiązywania zależności.
najpopularniejszych dystrybucji.
Dokładne informacje na temat wykorzy- sockaddr, sockaddr_in, in_addr – adresy
Wykorzystanie aplikacji tcpflow wygląda stania programu Wireshark do analizy pakie- Teraz sytuacja trochę się skomplikuje. Mamy bardzo podobnie jak w przypadku tcpdump. tów znajdziesz w cyklu artykułów Analiza pa- bowiem trzy struktury (jeżeli korzystamy je-Aby rozpocząć zapisywanie strumieni do od- kietów sieciowych, opublikowanym w nume- dynie z wersji czwartej protokołu IP), które powiadających im plików, należy wydać po- rach: kwietniowym, czerwcowym oraz wa- służą do przechowywania adresów. Skąd bie-lecenie:
kacyjnym Linux+. Jeżeli nie czytałeś jeszcze rze się taka różnorodność i jak sobie z nią po-
tych artykułów, to gorąco Cię do tego zachę- radzić?
tcpflow -i interfejs filtry
cam – nauczą Cię one nie tylko obsługi apli-
Struktura sockaddr jest najogólniejszą
kacji Wireshark, lecz również zwiększą Twoją strukturą przechowującą adresy gniazd – nie
Tcpflow, podobnie jak tcpdump, korzysta z bi- wiedzę na temat działania sieci w ogóle, co po- jest ona ograniczona w żaden sposób do kon-blioteki libpcap, oferując przez to taką samą zwoli na szybsze rozwiązywanie problemów z kretnej rodziny protokołów (którą definiuje-składnię wyrażeń filtrujących.
tworzonymi aplikacjami.
my przypisując odpowiednią wartość skła-
dowej sa_family). W praktyce nie będziesz
Listing 6. Użycie funkcji accept()
jednak korzystał z tej struktury – ręczne wpi-
#include
sywanie danych do tablicy sa_data mija się
#include
z celem.
Dla protokołu IPv4 odpowiednie jest wy-
int accept(int sockfd, // deskryptor pliku gniazda
korzystanie struktury sockaddr_in (istnieje
struct sockaddr* addr, // adres gniazda
jej odpowiednik dla protokołu IPv6 – soc-
socklen_t* addrlen); // wielkość struktury addr
kaddr_in6). Zapisane są w niej pełne infor-
macje o adresie sieciowym: adres IP (w po-
// wywołujemy funkcje getaddrinfo(), socket(), bind() i listen() tak jak
staci struktury in_addr) oraz numer portu
na wcześniejszych listingach
docelowego (zmienna składowa sin_port).
Zwróć uwagę na zastosowanie tablicy obiek-
char* port = ”5000”; // wykorzystywany port
tów typu unsigned char – wypełniamy ją
const int backlog = 10; // maksymalna liczba połączeń
zerami w celu zapewnienia odpowiednie-
struct sockaddr remote_addr; // adres komputera inicjalizującego połączenie
go rozmiaru struktury sockaddr_in, takie-
socklen_t addr_size; // rozmiar struktury sockaddr
go samego jak struktury sockaddr. Dzięki
int newsockfd; // deskryptor nowego gniazda
temu, pomimo że większość wywołań sys-
temowych wymaga podania adresu w posta-
addr_size = sizeof(remote_addr);
ci struktury sockaddr, możemy dokonać rzu-
newsockfd = accept(sockfd, &remote_addr, &addr_size);
towania.
// możemy rozpocząć przesyłanie danych za pomocą gniazda newsockfd
Sam 32-bitowy adres IP zapisany jest w
strukturze in_addr w postaci zmiennej ty-
68
październik 2009
www.lpmagazine.org
69
Programowanie
Programowanie
Programowanie przy użyciu gniazd sieciowych
Programowanie przy użyciu gniazd sieciowych
pu unsigned int. Nie musisz jednak przej- ó W celu wymiany danych możemy rów- na jest wartość 0. W przeciwnym wypadku mować się sposobem kodowania adresu – ca-nież korzystać ze standardowych funk- zwracany jest kod błędu, który możesz na-
łą niezbędną pracę wykonają odpowiednie
cji send() i recv(), pod warunkiem, że stępnie zamienić na czytelną dla użytkowni-
funkcje.
połączymy się ze zdalnym hostem przy ka postać, przy pomocy funkcji gai_strer-
użyciu wywołania connect(). Pamię- ror().
Kolejność wywołań
taj, że dane w dalszym ciągu będą prze-
Na koniec przypomnę, że nie ma różni-
Kolejność, z jaką używamy wywołań syste-
syłane przy użyciu zawodnego protokołu cy, czy funkcji getaddrinfo() podamy ad-
mowych zależy ściśle od typu gniazda, a w
UDP;
res IP, czy nazwę domenową. W tym dru-
szczególności od wykorzystywanego protoko- ó Zamykamy deskryptor pliku przy pomo- gim przypadku, system sam zadba o wyko-
łu warstwy transportu.
cy wywołania close().
nanie odpowiedniego zapytania DNS i ak-
Dla gniazd korzystających z protokołu
tualizację odpowiedniego pola struktury ad-
TCP, kolejność wywołań systemowych jest Przygotowanie
drinfo.
następująca:
adresów – getaddrinfo()
Gdy uznamy, że uzyskana lista adresów
Zanim skorzystamy z wywołania socket() nie będzie już nam w dalszej części progra-
ó Wywołujemy funkcję getaddrinfo() w w celu uzyskania deskryptora pliku nowego mu potrzebna, warto ją zwolnić, korzysta-celu wpisania odpowiednich danych ad- gniazda, powinniśmy pobrać informacje o in- jąc z wywołania freeaddrinfo(), jako je-resowych do struktur;
terfejsie sieciowym naszego komputera oraz dyny parametr podając wskaźnik do począt-
ó Wywołujemy funkcję socket(), zwra- komputera, z którym nawiążemy połączenie. ku listy.
cającą deskryptor pliku gniazda. Ja- Korzystamy w tym celu z funkcji getaddrin-
Z tego powodu nigdy nie powinieneś
ko parametry wywołania wykorzystu- fo(), której sposób wywołania został przed- zmieniać wartości tego wskaźnika, ponieważ jemy składowe struktur, które uzupeł- stawiony na Listingu 2.
w takim przypadku zwolnienie pamięci było-
niliśmy przy pomocy funkcji getad-
Bardzo ważną rolę przy wywoła- by niemożliwe.
drinfo();
niu funkcji getaddrinfo() pełni struktu-
ó Jeżeli nasz program ma działać jako ra hints – zawiera ona informacje o inte- Utworzenie gniazda – socket() serwer, nasłuchujący i obsługujący po- resującej nas wersji protokołu IP oraz typie Po uzyskaniu niezbędnych informacji o adre-
łączenia od klientów, korzystamy z wy- gniazda (TCP lub UDP). Zwróć uwagę, że sach, możemy użyć wywołania systemowe-wołania systemowego bind(), służące- jeżeli chcemy otrzymać adresy IP naszego go tworzącego nowy deskryptor pliku gniaz-go do przypisania gniazda do konkret- komputera, powinniśmy składowej ai_flags da. Jako parametrów użyjemy składowych nego portu TCP. Nasłuchiwanie połą- przypisać wartość AI_PASSIVE. Jeżeli chce- struktury addrinfo z listy zwróconej przez czeń odbywa się przy pomocy funk- my uzyskać adresy IP innego komputera, wywołanie getaddrinfo(). Sposób wywo-cji listen(). Połączenia przychodzą- pozostawiamy wartość NULL.
łania funkcji socket() został przedstawiony
ce akceptujemy za pomocą funkcji ac-
Wywołanie getaddrinfo() zwraca na Listingu 3.
cept(), co powoduje utworzenie nowe- wyniki w postaci jednostronnie łączonej
Jak widać, wartością zwracaną przez
go gniazda, służącego do komunikacji z listy. Możesz zatem wybrać najodpowied- wywołanie socket() jest deskryptor pli-danym klientem;
niejszy w danej sytuacji adres IP, stosując ku gniazda. Jest to zmienna typu int. Jeże-
ó Jeżeli nasz program ma działać jako jedynie prosty algorytm przechodzenia li- li w trakcie działania funkcji wystąpił błąd, klient, nie potrzebujemy korzystać z wy- sty. Ostatni element listy rozpoznasz bez zwracana jest wartość -1, zaś zmienna glo-wołania bind(). Zamiast tego, łączymy problemów – jego składowa ai_next ma balna errno przybiera odpowiednią wartość.
się ze zdalnym procesem przy pomocy wartość NULL.
Można ją zamienić na postać tekstową, czy-
funkcji connect() – system automatycz-
Jeżeli wywołanie funkcji getaddrin- telną dla użytkownika, przy pomocy funkcji
nie przydzieli połączeniu odpowiedni port fo() zakończyło się sukcesem, to zwraca- perror().
po stronie naszej maszyny;
ó Wysyłamy i odbieramy dane, pamiętając Listing 7. Sposób użycia funkcji connect() i getpeername()
o ograniczeniach związanych z maksy-
#include
malną ilością danych wysyłanych w jed-
#include
nym datagramie;
ó Zamykamy deskryptor pliku gniazda przy
int connect(int sockfd, // deskryptor pliku gniazda
pomocy wywołań shutdown() i close().
struct sockaddr* serv_addr, // adres serwera
int addrlen); // rozmiar struktury serv_addr
Dla gniazd korzystających z protokołu UDP
przebieg typowej wymiany danych jest znacz-
int getpeername(int sockfd, // deskryptor pliku gniazda
nie prostszy:
struct sockaddr* addr, // struktura, w której zapisany
zostanie adresowych
ó Podobnie jak w przypadku gniazd TCP, int* addrlen); // rozmiar struktury addr
korzystamy z wywołań getaddrinfo()
i socket();
// wywołujemy funkcje getaddrinfo() i socket(), tak jak na wcześniejszych
ó Możemy już wysyłać i odbierać da-
listingach
ne przy pomocy funkcji sendto() i re-
connect(sockfd, nodeinf->ai_addr, nodeinf->ai_addrlen);
cvfrom();
68
październik 2009
www.lpmagazine.org
69
Programowanie
Programowanie
Programowanie przy użyciu gniazd sieciowych
Programowanie przy użyciu gniazd sieciowych
Przypisanie do portu – bind()
Akceptowanie
niona przez system – również jądro może li-
Po otrzymaniu prawidłowego deskryptora połączeń – accept()
mitować maksymalną liczbę oczekujących
pliku gniazda, możemy powiązać je z odpo- Po rozpoczęciu nasłuchiwania, możemy ak- połączeń.
wiednim portem w naszym systemie. Wyko- ceptować przychodzące połączenia przy po-
rzystujemy w tym celu funkcję bind(), któ- mocy wywołania accept(). Sposób jego uży- Nawiązanie
rej sposób wywołania został przedstawiony cia przedstawiony został na Listingu 6.
połączenia – connect()
na Listingu 4.
Zaakceptowanie oczekującego połącze- Jeżeli nasza aplikacja ma służyć jedynie do na-
Działanie wywołania bind() nie powin- nia spowoduje utworzenie nowego deskryp- wiązywania połączenia z serwerem, to może-no budzić żadnych wątpliwości. Pamiętaj jed- tora pliku gniazda. Za jego pomocą możemy my pominąć wywołanie funkcji bind() i od nak, że porty 0 – 1023 są dostępne jedynie dla komunikować się ze zdalnym procesem za po- razu skorzystać z funkcji connect(), odpo-użytkownika root.
mocą funkcji send() i recv(). Gniazdo, któ- wiadającej za nawiązanie połączenia ze zdal-
re przypisane jest do interfejsu naszego kom- nym hostem. Sposób użycia wywołania con-
Nasłuchiwanie
putera pozostaje dalej otwarte, nasłuchując na- nect() został przedstawiony na Listingu 7.
połączeń – listen()
stępnych połączeń.
Po wywołaniu zakończonym sukcesem
Po powiązaniu gniazda z portem naszego sys-
Adres komputera nawiązującego po- (w takim przypadku zwracana jest wartość
temu, możemy rozpocząć nasłuchiwanie po- łączenie z naszym serwerem przechowuje- 0), możemy już użyć funkcji send() i recv() łączeń przy użyciu wywołania systemowe- my w strukturze remote_addr, zaś zmien- w celu wymiany danych pomiędzy procesami go listen(), którego sposób użycia został na addr_size zawiera jej rozmiar wyrażo- działającymi na połączonych maszynach.
przedstawiony na Listingu 5.
ny w bajtach.
Nazwę zdalnego komputera możesz odczy-
Funkcja listen() zwraca wartość 0 w
Duże znaczenie ma wielkość bufora po- tać przy pomocy funkcji getpeername(), któ-
przypadku, gdy rozpoczęcie nasłuchiwa- łączeń, określona za pomocą zmiennej bac- rej sposób wywołania został również przedsta-nia zakończyło się powodzeniem oraz -1 w klog – jeżeli zostanie przekroczona, dalsze wiony na Listingu 7. Wykorzystywana funkcja przypadku, gdy wystąpił błąd. Komunikat o połączenia będą automatycznie odrzuca- inet_ntop() pozwala na przedstawienie adre-błędzie możesz przedstawić użytkownikowi ne. Należy pamiętać, że nie zawsze żądana su hosta (zapisanego w strukturze sockaddr) w przy pomocy funkcji perror().
wielkość bufora połączeń zostanie uwzględ- postaci czytelnej dla użytkownika.
Listing 8. Sposób użycia funkcji send() i recv()
Transmisja
#include
danych – send() i recv()
#include
Gdy nawiążemy połączenie ze zdalnym ho-
stem lub zaakceptujemy żądanie połączenia,
int send(int sockfd, // deskryptor pliku gniazda
możemy rozpocząć przesyłanie danych. Słu-
const void* msg, // wskaźnik na przesyłane dane
żą w tym celu funkcje send() i recv(), któ-
int len, // rozmiar danych w bajtach
rych wykorzystanie przedstawione zostało na
int flags); // flagi kontrolne
Listingu 8.
Funkcja send() po wywołaniu zwraca
int recv(int sockfd, // deskryptor pliku gniazda
ilość wysłanych danych, wyrażoną w bajtach
void* buf, // bufor, do którego zapisane zostaną dane
lub -1, jeżeli wystąpił błąd. Pamiętaj, że jeżeli
int len, // rozmiar bufora
przekroczysz maksymalną jednostkę transmi-
int flags); // flagi kontrolne
syjną sieci, to będziesz musiał ponownie wy-
wołać funkcję send() w celu przesłania resz-
Listing 9. Parametry wywołania funkcji sendto() i recvfrom()
ty danych. Aby uniknąć takiej sytuacji, war-
#include
to wysyłać dane w porcjach nie przekracza-
#include
jących wielkości 1 KB. Optymalny rozmiar
zależy oczywiście od interfejsu sieciowego
int sendto(int sockfd, // deskryptor pliku gniazda
– możesz przekonać się o tym wywołując pro-
const void* msg, // dane do wysłania
gram ifconfig. Pamiętaj, że pakiet, oprócz ob-
int len, // rozmiar danych wyrażony w bajtach
szaru danych, zawiera również nagłówki, któ-
unsigned int flags, // flagi kontrolne
rych rozmiar powinieneś odjąć od wielkości
const struct sockaddr* to, // adres docelowy
MTU w celu uzyskania maksymalnego roz-
socklen_t tolen); // rozmiar struktury to
miaru pakietu, który może zostać przesłany za
pomocą danego interfejsu.
int recvfrom(int sockfd, // deskryptor pliku gniazda
Funkcja recv() zwraca liczbę bajtów za-
void* buf, // bufor otrzymywanych danych
pisaną w buforze lub -1, jeżeli wystąpił błąd.
int len, // rozmiar bufora
Zwrócenie wartości 0 oznacza, iż zdalny host
unsigned int flags, // flagi kontrolne
zamknął połączenie. Podając prawidłowy roz-
struct sockaddr* from, // adres źródłowy
miar len bufora, zgodny z wielkością zare-
int* fromlen); // rozmiar struktury from
zerwowanego obszaru pamięci, zapobiegamy
przypadkowemu nadpisaniu obszarów przy-
legających.
70
październik 2009
www.lpmagazine.org
71
Programowanie
Programowanie
Programowanie przy użyciu gniazd sieciowych
Programowanie przy użyciu gniazd sieciowych
Dodatkowe informacje na temat działa- stało świetnie opisane w tutorialu Beej's Guide fejs sieciowy naszego komputera. Pozwala na nia funkcji send() i recv(), a w szczególno- to Network Programming Using Internet Soc- przełączenie karty sieciowej w tryb promisco-
ści flagi kontrolne, znajdziesz w ich dokumen- kets, autorstwa Briana Halla. Jest on dostępny us, pozwalający na odbieranie danych nieprze-tacji (man send, man recv).
na stronie wymienionej w ramce W Sieci.
znaczonych dla naszego komputera. Podobnie
jak libnet, biblioteka ta jest wykorzystywana
Gniazda UDP
Definiowanie protokołów
głównie w aplikacjach powiązanych z bezpie-
– sendto() i recvfrom()
Za pomocą gniazd możemy wysyłać dowol- czeństwem sieci.
W przypadku gniazd UDP, po otrzymaniu ne dane. Bardzo ważne jest jednak, aby były
deskryptora pliku gniazda, możemy od razu one poprawnie interpretowane przez odbie- Podsumowanie przystąpić do wysyłania i odbierania danych rający je program. W celu określenia zasad, Jeżeli po lekturze artykułu odczuwasz niedo-za pomocą funkcji sendto() i recvfrom(). z jakimi należy budować komunikaty, a na- syt, to gorąco zachęcam Cię do zapoznania się Sposób ich wykorzystania został przedstawio- stępnie wydobywać z nich istotne dane, two- z tutorialem, wymienionym w poprzednim pa-ny na Listingu 9.
rzy się protokoły.
ragrafie. Na szczególną uwagę zasługują za-
Z pewnością zauważyłeś bardzo duże po-
Istnieje wiele typów protokołów. Jedna z prezentowane w nim przykłady aplikacji ko-
dobieństwo do funkcji send() i recv(). Ko- najpopularniejszych klasyfikacji dzieli je na rzystających z gniazd, które z oczywistych po-rzystając z funkcji sendto() i recvfrom() protokoły binarne i tekstowe (znakowe). W wodów ciężko zawrzeć w artykule.
wystarczy dodatkowo podać adres docelowy protokołach binarnych, dane zapisywane są
Oprócz tutoriali, polecam Ci również
lub źródłowy. Jeżeli wysyłasz (lub odbierasz) za pomocą sekwencji bitów, stanowiących re- zapoznanie się ze stronami podręczni-wiele danych do jednego hosta, to warto się z prezentację przesyłanych informacji. W proto- ka systemowego. Znajdujące się tam opi-nim połączyć przy użyciu funkcji connect(). kołach tekstowych, dane zapisywane są za po- sy działania funkcji z pewnością są do-mocą znaków (np. ASCII), przez co są zro- kładniejsze od zawartych w tym artykule.
Zakończenie połączenia
zumiałe dla człowieka (ang. human reada- Głównym moim celem było przekazanie Ci
Po zakończeniu wymiany danych należy za- ble protocol).
wiedzy na temat kolejności i sposobu wy-
mknąć gniazdo przy pomocy funkcji close(),
woływania poszczególnych funkcji – nie
jako jedyny parametr wywołania podając de- Libnet i libpcap
da się jej bowiem zdobyć, czytając jedy-
skryptor pliku gniazda.
Omawiane w artykule techniki mają bar- nie strony podręcznika. Początkujący pro-
Dla gniazd TCP powinniśmy przed wy- dzo szerokie zastosowanie w pisaniu stan- gramiści często gubią się również w gąsz-wołaniem funkcji close() zakończyć połą- dardowych aplikacji sieciowych. Nie dają czu struktur odpowiadających za przecho-czenie przy pomocy funkcji shutdown().
nam jednak wielkich możliwości, jeżeli cho- wywanie adresów gniazd, które również
dzi o manipulowanie nagłówkami warstwy nie zostały dobrze opisane.
Efektywna obsługa gniazd
sieciowej i transportu. W większości aplika-
Gdy zdobędziesz już większą wprawę w
Wykorzystując informacje zawarte w artyku- cji jest to zupełnie niepotrzebne, co więcej programowaniu sieciowym, polecam Ci za-le, z pewnością zauważyłeś, że wywołanie du- – często może powodować spore niedogod- poznanie się z podstawowymi dokumentami żej części funkcji służących do obsługi gniazd ności, ponieważ w celu uzyskania niskopo- RFC, dotyczącymi najważniejszych protoko-powoduje zablokowanie wykonywania dalszej ziomowego dostępu do mechanizmów sie- łów warstwy aplikacji. Po ich lekturze, spró-
części programu. Dzieje się tak, ponieważ w sy- ciowych, konieczne są uprawnienia użyt- buj zaimplementować niektóre z ich funk-tuacji, gdy nie ma oczekujących danych lub po- kownika root.
cji w programach – jest to bardzo pouczają-
łączeń, wywołanie systemowe oczekuje na ich
Libnet jest biblioteką bardzo szero- ce doświadczenie, szczególnie jeżeli monito-
nadejście. W większości programów jest to bar- ko wykorzystywaną w aplikacjach związa- rujesz wychodzące dane przy użyciu sniffera, dzo niepożądane.
nych z bezpieczeństwem sieci. Pozwala na a następnie analizujesz je za pomocą progra-
Podczas tworzenia nowego gniazda mamy wstrzykiwanie pakietów o zdefiniowanej mu Wireshark.
możliwość sprawienia, aby było ono niebloku- przez nas zawartości nagłówków warstwy
Jeżeli masz jakieś wątpliwości lub proble-
jące, jednak powoduje to dość duże problemy sieciowej i transportu. Wykorzystanie bi- my związane z programowaniem przy użyciu związane z jego obsługą – konieczne jest jego blioteki libnet nie jest trudne, jednak wyma- gniazd, to z chęcią na nie odpowiem – mój ad-odpytywanie, co z kolei pochłania dużą licz- ga znacznie większej wiedzy na temat budo- res e-mail znajduje się w ramce O Autorze.
bę cykli zegarowych. Jakie jest więc optymal- wy i zadań warstw niższych, niż w przypad-
ne rozwiązanie?
ku standardowego programowania przy uży-
Możliwość jednoczesnego monitorowania ciu gniazd.
i wykorzystania wielu gniazd sieciowych daje
Libpcap to biblioteka służąca do prze-
nam funkcja select(). Jej zastosowanie zo- chwytywania danych odbieranych przez inter-
O autorze
Autor interesuje się bezpieczeństwem
W Sieci
systemów informatycznych, programo-
waniem, elektroniką, muzyką rockową,
ó Główna strona programu Wireshark – http://www.wireshark.org/;
architekturą mikroprocesorów oraz za-
ó Główna strona programu tcpflow – http://www.circlemud.org/~jelson/software/tcpflow/;
stosowaniem Linuksa w systemach wbu-
ó Wspomniany tutorial – http://www.beej.us/guide/bgnet/;
dowanych.
ó Biblioteka libnet – http://sourceforge.net/projects/libnet-dev/;
Kontakt z autorem: rkulaga89@gmail.com
ó Biblioteka libpcap – http://sourceforge.net/projects/libpcap/.
70
październik 2009
www.lpmagazine.org
71
Wyszukiwarka
Podobne podstrony:
2009 10 OpenCV systemy wizyjn Nieznany
2009 10 Jurassic Park Clonezill Nieznany
2009 10 IMB ochrona przed korozja
EGZAMIN 2009 10
2009 10 27 Wstęp do SI [w 04]id&835
Przy winie Nieznany
Serie (5) Zadan Trudnych 2009 10 Osekowski p5
2009 10 STATYSTYKA PARAMETRY Z PROBY
Historia 2009 10 etap rejonowy odp
2009 10 Playing Fetch Building a Dedicated Download System with Rtorrent
K2 2009 10 zad 1
2009 10 Akwizycja i analiza pamięci
2009 10 Secret Stick a Usb Dongle for One Time Passwords
Program Wyborczy PSPNP Nieznany
2 10 Wielkie odkrycia geografic Nieznany
E1 2009 10 zad 3
Zagadnienia Egz 2009 10
więcej podobnych podstron