2009 10 Programowanie przy uzyc Nieznany


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