socket()
,
socketpair()
,
DEFINICJA: int socket (int family, int type, int protocol); WYNIK: deskryptor gniazda gdy gniazdo zostanie utworzone, -1 gdy blad: errno = EINVAL (bledne dane) EPROTONOSUPPORT (wartosci typu i protokolu nie sa dozwolone w podanej dziedzinie) EMFILE (pelna tablica deskryptorow procesu) ENFILE (pelna systemowa tablica plikow) EACCES (brak uprawnien do utworzenia gniazda o podanych parametrach) ENOSR (brak zasobow systemowych (wolnego i-wezla)) ENOBUFS (brak pamieci na bufory gniazda)Argument
family
oznacza domene komunikacyjna (czyli rodzine
protokolow/adresow - stala AF_XXX
(PF_XXX
)) w jakiej bedzie funkcjonowac
nowo utworzone gniazdo, type
okresla rodzaj
gniazda, zas protocol
- protokol z jakiego gniazdo korzysta.
Omowienie rodzin protokolow, typow gniazd oraz stalych z nimi zwiazanych znajduje sie w rozdziale 9.1.2.
Parametr protocol
czesto jest determinowany przez rodzine i rodzaj
gniazda (np: family=AF_INET
i type=SOCK_DGRAM
implikuje
protocol=IPPROTO_UDP) w takich przypadkach podanie jako argument
protocol
wartosci 0 wymusza przyjecie domyslnego protokolu.
Ponizsza tabela wymienia relacje pomiedzy typem gniazd i protokolem (rodzina
AF_INET):
type | protocol | wlasciwy protokol |
SOCK_DGRAM | IPPROTO_UDP | UDP |
SOCK_STREAM | IPPROTO_TCP | TCP |
SOCK_SEQPACKET | IPPROTO_TCP | TCP |
SOCK_RAW | IPPROTO_ICMP | ICMP |
SOCK_RAW | IPPROTO_RAW | "surowy" |
W dziedzinie Unixa jedynymi dostepnymi typami gniazd sa SOCK_STREAM i SOCK_DGRAM (podanie wartosci SOCK_RAW nie powoduje bledu - rzeczywisty typ jest ustawiany na SOCK_DGRAM).
sys_socket()
{ if (niemozlwe znalezienie w tablicy proto[] struktury operacji dla rodziny family) /* patrz: rozdzial 9.1.2 opis struktury proto_ops */ probuj zaladowac odpowiedni modul jadra i wykonaj powyzsza czynnosc jeszcze raz; if (niepowodzenie) return -EINVAL; if (nieprawidlowe argumenty type lub protocol) return -EINVAL; pobierz pusty i-wezel; ustaw w nim nastepujace pola: i_mode = S_IFSOCK; /* znacznik: "jestem zwiazany z gniazdem" */ i_sock = 1; i_gid, i_uid odpowiednio; zainicjuj pola w strukturze socket (umieszczonej w polu s.socket_i) i-wezla: state = SS_UNCONNECTED; /* inicjalny stan gniazda */ wartosci pozostalych pol oczywiste (odsylam do kodu); zwieksz o jeden licznik globalnej ilosci gniazd w systemie; wywolaj odpowiednia funkcji tworzenia gniazda z poziomu dziedzin; }Poziom dziedzin komunikacyjnych:
protocol
, sprawdzeniu
praw (czy nie probujemy utworzyc gniazda surowego bez uprawnien nadzorcy) i paru innych dosc oczywistych rzeczy inicjalizowane sa struktury
sock
opisujace szczegoly gniazda na poziomie dziedzin.
Polecam przestudiowanie opisu pol struktury sock
- rozdzial 9.1.2 oraz kodu
ponizszych funkcyj:
Dziedzina Internetu:
(plik: net/ipv4/af_inet.c
)
int inet_create(struct socket
*sock, int protocol);
Tutaj ustawiamy w strukturze sock
miedzy innymi:
mtu = 576
- maksymalny element transmisji
oraz rozne pola zwiazane z protokolem TCP
Dziedzina Unixa:
(plik: net/unix/af_unix.c
)
int unix_create(struct socket *sock, int protocol);
Wartosc pola mtu
ustalana jest na 4096.
W obu przypadkach inicjalizowane sa nastepujace pola struktury
sock
:
type
- typ taki sam jak w strukturze socket
,
state_change, data_ready, write_space, error_report
(patrz:
rozdzial 9.1.10 o sygnalach)
kolejki: write_queue, read_queue i receive_queue
state = TCP_CLOSE
sndbuf, rcvbuf
(te wartosci mozna zmienic - patrz: opcje
SO_SNDBUF i SO_RCVBUF
DEFINICJA: int socketpair(int family, int type, int protocol, int sockvec[2]); WYNIK: 0 w przypadku sukcesu -1 gdy blad, errno = EOPNOSUPP (protokol nie udostepnia socketpair) EINVAL (brak zdefiniowanej funkcji socketpair dla dziedziny) + kody bledow funkcji socket()Gdy nie wystapi blad, do wektora
sockvec
jest zapisana para deskryptorow gniazd strumieniowych dziedziny Unixa. Otrzymujemy w ten sposob lacze strumieniowe (ang. stream pipe), ktore od lacza komunikacyjnego IPC rozni sie mozliwoscia dwustronnej komunikacji.
Na najwyzszym poziomie - funkcja sys_socketpair():
{ utworz pierwsze gniazdo (za pomoca socket() - w razie bledu zwraca jego kod); jesli gniazdo nie udostepnia operacji "dziedzinowej" sockatpair (ops->socketpair == NULL) return -EINVAL; utworz drugie gniazdo; wywolaj funkcje socketpair z poziomu dziedzin; polacz gniazda na poziomie struktur (wskaznikiem conn); ustaw stan obu gniazd na SS_CONNECTED; wpisz deskryptory gniazd do tablicy sockvec[]; return 0; }Na poziomie dziedzin innych niz Unix odpowiednie funkcje zwracaja jedynie
-EOPNOSUPP
.
Funkcja int unix_socketpair(struct socket *a, struct socket *b)
dziala nastepujaco:
{ polacz gniazda na poziomie struktur sock (wskaznikiem protinfo.other) ustaw stan gniazda na TCP_ESTABILISHED }
socketpair()
moga
sluzyc do przekazywania deskryptorow plikow (tej mozliwosci nie daja
mechanizmy IPC). Opis mechanizmu przekazywania deskryptorow i specjalnej
struktury cmsghdr
z nim zwiazanej - w rozdziale 9.1.2.
socketpair()
oraz bind()
mozemy stworzyc strumieniowe lacze nazwane.
include/linux/socket.h
:
struct sockaddr { unsigned short sa_family; /* rodzina adresow, AF_xxx */ char sa_data[14]; /* co najwyzej 14 bajtow addresu wlasciwego */ /* protokolu */ };Zawartosc ostatnich 14 bajtow powyzszej struktury jest interpretowana odpowiednio od rodzaju adresu.
include/linux/in.h
znajduja sie nastepujace definicje
struktur przechowujacych adresy Internetowe:
/* Adres internetowy */ struct in_addr { __u32 s_addr; /* 32 bitowy id.sieci/id.stacji */ }; /* Struktura opisujaca adres Internetowy (IP) dla gniazd. */ #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ struct sockaddr_in { short int sin_family; /* Rodzina adresow */ unsigned short int sin_port; /* Numer portu */ struct in_addr sin_addr; /* Adres internetowy */ /* Nieuzywany kawalek struktury sockaddr */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; };
include/linux/un.h
znajdziemy nastepujace definicje:
#define UNIX_PATH_MAX 108 /* Maksymalna dlugosc sciezki - */ /* nazwy gniazda w dziedzinie Unixa */ /* Struktura opisujaca adres w dziedzinie Unixa. */ struct sockaddr_un { unsigned short sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* nazwa sciezki */ };We wszystkich funkcjach wymagajacych podania adresu gniazda, podajemy wskaznik do odpowiedniej struktury (
sockaddr_in
,
sockaddr_un
lub innej) zrzutowany na wskaznik do struktury ogolnej
sockaddr
i uzupelniamy go o rozmiar struktury wlasciwej dla
protokolu.
bind()
Gniazda tworzone za pomoca funkcji socket()
sa anonimowe. Aby do gniazda
mogly dotrzec jakies dane, wymagane jest przypisanie mu jednoznacznie
identyfikujacej go nazwy (ang. binding), czyli adresu sieciowego komputera i numeru portu
(w dziedzinie Internetu), badz nazwy sciezkowej (dziedzina Unixa).
Do tego celu sluzy nastepujaca funkcja.
DEFINICJA: int bind(int fd, struct sockaddr *my_addr, int addrlen) WYNIK: 0 w przypadku sukcesu -1, gdy blad: errno = [bledy zglaszane na najwyzszym poziomie] EBADF (fd nie jest poprawnym deskryptorem) ENOTSOCK (fd nie jest deskryptorem gniazda) EADDRINUSE (adres juz zarezerwowany przez inne gniazdo) EINVAL (zla wartosc addrlen jako dlugosci adresu danej dziedziny, gniazdo juz ma przypisany adres) [bledy specyficzne dla AF_INET] EADDRNOTAVAIL (podany adres nie jest osiagalny) EACCES (brak praw do uzywania podanego adresu, np. proba uzycia zastrzezonego numeru portu przez zwyklego uzytkownika) [bledy specyficzne dla AF_UNIX] EINOMEM (brak pamieci na adres)Parametry:
fd
- deskryptor gniazda, my_addr
- wskaznik na strukture adresu odpowiednia dla rodziny
protokolow, do ktorej nalezy gniazdo, addrlen
-
rozmiar tej struktury.
sys_bind()
)
{ if (fd nie jest poprawnym deskryptorem) return -EBADF; if (deskryptor nie wskazuje na gniazdo) return -ENOTSOCK; wywolaj bind() z poziomu dziedzin; }Poziom dziedzin:
Dziedzina Internetu:
(plik: net/ipv4/af_inet.c)
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
(sk
oznacza wskaznik na strukture sock
opisujaca
gniazdo).
{ if (zdefiniowana operacja bind na poziomie protokolow) wywolaj ja; else { if (stan gniazda != TCP_CLOSE || addr_len < sizeof(struct sockaddr_in)) return -EINVAL; /* Sprawdzanie numeru portu */ if (podany numer portu == 0) znajdz wolny numer portu; if (podany numer portu < PROT_SOCK a proces nie ma praw nadzorcy) return -EACCES; } wywolaj funkcje ip_chk_addr(adres) (zdefiniowana w pliku net/ipv4/devinet.c) ktora sprawdza, czy podany adres jest rzeczywiscie adresem naszego komputera, czy tez jest adresem podsieci (uzywanym do broadcastingu lub multicastingu); jesli aktualny proces ma uprawnienia nadzorcy - pozwol mu przypisac gniazdu dowolny adres, zwykly uzytkownikowi moze tylko ustawic adres lokalny ( blad: EADDRNOTAVAIL ); if (multi(broad)casting) sk->saddr = 0; /* dajemy znac urzadzeniu sieciowemu */ else sk->saddr = uaddr->sin_addr.s_addr; przechodz po liscie umieszczonej pod indeksem numer portu mod SOCK_ARRAY_SIZE-1 w tablicy haszujacej sock_array; if (na liscie jest gniazdo o tym samym numerze portu bez ustawionej flagi reuse, lub nasze gniazdo nie ma ustawionej flagi reuse) return -EADDRINUSE; ustaw nastepujace pola: dummy_th.source = numer portu; (dummy_th jest neglowkiem tcp) daddr = 0; dummy_th.dest = 0; }
UWAGI:
bind
struktury proto jest ustawione na NULL (patrz opis struct proto
w rozdziale 9.1.2), zatem nie bedzie wywolywania bind()
na
poziomie protokolow.
#define PROT_SOCK 1024;Porty o numerach <
PROT_SOCK
moga byc przydzielane tylko procesom z prawami
nadzorcy.
bind()
ustalony numer portu
wkrotce po zamknieciu gniazda korzystajacego z tego
samego numeru portu (np. przerywajac i wznawiajac dzialanie programu
wykorzystujacego ustalony numer portu), to bind()
zwroci
EADDRINUSE
.
Dlaczego sie tek dzieje ?
Otoz po zamknieciu gniazda przy pomocy
close()
gniazdo nie jest natychmiast usuwane z
tablicy haszujacej gniazd (opis struktur danych - rozdzial 9.1.2), tylko
pozostaje w niej, zmieniajac jedynie swoj stan na TCP_TIME_WAIT.
Podczas przesylania danych zdarza sie bowiem, ze przesylany komunikat
zostanie uznany za zaginiony, a w rzeczywistosci bedzie
przetrzymywany przez jakis router pomiedzy stacja zrodlowa a docelowa.
Przed wyslaniem komunikatu w dalsza droge polaczenie moze zostac
zamkniete przez strone oczekujaca na ,,zaginiony'' pakiet.
Jesli natychmiast zostanie stworzone gniazdo
na tym samym porcie, co przed chwila zamkniete,
to moze sie zdarzyc, ze zawieruszony komunikat dotrze do
nowego gniazda. Dlatego tez standartowo po stronie zamykajacej polaczenie
gniazdo pozostaje w stanie TCP_TIME_WAIT przez czas okreslony stala
(zdefiniowana w pliku: include/net/tcp.h)
#define TCP_TIMEWAIT_LEN (60*Hz) /* Czyli 60 sek. */(w innych systemach od 20 sek. do 4 min.) W czasie pobytu gniazda w stanie
TCP_TIME_WAIT
ignorowane sa nadchodzace komunikaty.
Istnieje jednak mozliwosc wymuszenia przydzielania gniazdu numeru
zablokownego portu, opisana w rozdziale 9.1.5 o opcjach gniazd (opcja SO_REUSEADDR
). Uzywanie tej
opcji moze byc jednak niebezpieczne - narazamy sie na otrzymanie smieci z powodow
opisanych powyzej.
bind()
jest obowiazkowe dla procesu - serwera. Klient z
reguly nie musi bezposrednio wywolywac bind()
- proba nawiazania polaczenia
(connect()
) w protokole bezpolaczeniowym badz wyslania danych automatycznie przypisze
gniazdu klienta efemeryczny numer portu i dostarczy go serwerowi. Automatyczne odszukanie
wolnego numeru portu wykonuje (dla gniazda Internetowego)
funkcja int inet_autobind(struct sock* sk);
Funkcja ta nie robi nic, jesli gniazdo ma juz przydzielony numer portu. W
przeciwnym przypadku szuka wolnego numeru portu przegladjac zbior wszystkich
gniazd umieszczonych w tablicy haszujacej sock_array[]
.
Dziedzina Unixa (plik:net/unix/af_unix.c
)
static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addrlen)
UWAGA:
{ if (nazwa gniazda != NULL || zla wartosc addrlen) return -EINVAL; zakoncz NULL'em podana nazwe sciezki; if(przydzielony i-wezel w filesystemie dla gniazda) return -EINVAL; /* Mamy juz przydzielona nazwe */ zapisz nazwe sciezki w polu protinfo.af_unix.name struktury sock; utworz plik specjalny o podanej nazwie; /* funkcja mknod */ znajdz odpowiadajacy mu i-wezel; /* funkcja namei */ umiesc wskaznik na utworzony i-wezel w polu protinfo.af_unix.inode; return 0; }
getsockname()
Przekazuje nazwe zwiazana z gniazdem poprzez asocjacje. Dzieki niej mozna poznac numer portu automatycznie przydzielony prez system.
DEFINICJA: int getsockname(int fd, struct sockaddr *sockaddr, int *sockaddrlen) WYNIK: 0 w przypadku sukcesu -1 gdy blad: errno = EBADF (fd nie jest deskkryptorem) ENOTSOCK (deskryptor fd nie wskazuje na gniazdo)Adres zwiazany z gniazdem jest zapisywany pod adresem pamieci przekazanym jako
sockaddr
(musi byc uprzednio
zaalokowane miejsce!). Pod adresem
sockaddrlen
zapisana zostaje wielkosc struktury przechowujacej
adres.
Jest oczywista. Dzialanie funkcji sprowadza sie do zwrocenia
danych zapisanych w polach struktury
sock
:
W dziedzinie Internetu adres przechowywany jest w polach rcv_addr
i
saddr
(moze go tam nie byc jesli gniazdo jest dopiero co
stworzone, wtedy pobierany jest adres komputera z warstwy IP). Numer portu
zapisany jest w polu dummy_th.source
.
W dziedzinie Unixa lancuch bedacy nazwa gniazda pamietany jest w polu
protoinfo.af_unix.name
struktury sock
.
getpeername()
DEFINICJA: int getpeername(int fd, struct sockaddr *peeraddr, int *peeraddrlen) WYNIK: 0 w przypadku sukcesu -1 gdy blad: errno = EBADF (fd nie jest deskkryptorem) ENOTSOCK (deskryptor fd nie wskazuje na gniazdo) ENOTCONN (brak polaczenia)Analogicznie jak w funkcji
getsockname()
parametr
fd
oznacza deskryptor gniazda, zas peeraddr
i
peeraddrlen
- adresy pod ktorymi beda zapisane: adres partnera
i dlugosc adresu.
sock
W dziedzinie Internetu numer portu procesu odleglego przechowywany
jest w polu dummy_th.dest
,
zas jego adres w polu daddr
. (Wartosci tych pol sa ustawiane
podczas nawiazywania polaczen).
W dziedzinie Unixa pobieramy nazwe gniazdka partnera odwolujac sie do
niego bezposrednio (pole protinfo.af_unix.other
struktury
sock
wskazuje na gniazdo, z ktorym jestesmy polaczeni).
include/linux/net.h
(struktury: socket
, proto_ops
oraz definicje stalych)
include/net/sock.h
(definicje struktur sock
i proto
)
net/socket.h
(definicja struktury sockaddr
)
net/socket.c
(implementacja funkcji systemowych - najwyzszy poziom)
net/ipv4/af_inet.c
(implementacja funkcji systemowych - dziedzina Internetu)
net/unix/af_unix.c
(implementacja funkcji systemowych - dziedzina Unixa)