Do spisu tresci tematu 9

9.1.6 Tworzenie i nazwy gniazd




Spis tresci


Tworzenie gniazd

Uzytkownik identyfikuje gniazda za pomoca deskryptorow (analogicznie jak pliki lub obiekty IPC). Zatem funkcje tworzace gniazdo zwracaja deskryptor, wykorzystywany pozniej przy kazdorazowym odwolywaniu sie do gniazdka.

Funkcja socket()

Funkcja tworzy nowe gniazdo.
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).

Implementacja funkcji:

Najwyzszy poziom: funkcja 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:
Po sprawdzeniu zgodnosci typu gniazda z wartoscia 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


Funkcja socketpair()

Funkcja ta zaimplementowana jest tylko dla dziedziny Unixa, sluzy do tworzenia lacza komunikacyjnego miedzy procesami.
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.

Implementacja

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
}


UWAGI:

  • Lacza strumieniowe utworzone za pomoca 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.
  • Poslugujac sie funkcjami socketpair() oraz bind() mozemy stworzyc strumieniowe lacze nazwane.

    Nazwy gniazd

    Mowiac o gniazdkach, czesto uzywamy zamiennie pojec nazwy i adresu. Slowo adres moze jednak byc mylace w odniesieniu do dziedziny Unixa, gdzie nie korzystamy z rzeczywistych adresow sieciowych, ale identyfikujemy gniazda sciezkami plikowymi.

    Struktury przechowujace nazwy gniazd gniazd

    Wiele funkcji dzialajacych na gniazdkach wymaga podania struktury reprezentujacej adres gniazda. Jej definicja znajduje sie w pliku naglowkowym 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.

    Dziedzina Internetu

    W pliku 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)];
    };
    

    Dziedzina Unixa

    W pliku naglowkowym 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.

    Ustanawianie nazwy gniazda - funkcja 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.

    Implementacja funkcji:

    Najwyzszy poziom: (funkcja 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:

  • W interesujacych nas protokolach (TCP,UDP) pole bind struktury proto jest ustawione na NULL (patrz opis struct proto w rozdziale 9.1.2), zatem nie bedzie wywolywania bind() na poziomie protokolow.

  • W strukturze sockaddr_in podajemy numer portu. W pliku include/net/sock.h znajdziemy definicje stalej:
    #define PROT_SOCK     1024;
    
    Porty o numerach < PROT_SOCK moga byc przydzielane tylko procesom z prawami nadzorcy.

  • Jesli utworzymy nowe gniazdo w protokole TCP i przypiszemy mu za pomoca 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.

  • Wywolanie funkcji 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)


    { 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; }
    UWAGA:

  • W dziedzinie Unixa mozliwosc automatycznego nazywania gniazda nie istnieje, gdyz gniazda identyfikowane sa nazwami sciezek. Zlecenie jadru automatycznego tworzenia nazwy dla gniazda mogloby spowodowac efekty uboczne (np. proba tworzenia pliku w katalogu bez praw dostepu lub niejednoznacznosc nazw).

    Pobieranie nazw gniazd


    Funkcja 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.

    Implementacja:

    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.

    Funkcja getpeername()

    Zwraca nazwe gniazda procesu partnera, ktory jest polaczony z danym gniazdem.

    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.

    Implementacja:

    Dzialanie funkcji sprowadza sie do sprawdzenia, czy gniazdo jest w stanie polaczenia, (jesli nie - sygnalizowany jest blad: ENOTCONN), a nastepnie zwrocenia wartosci przechowywanych w strukturze 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).


    Bibliografia

    1. Pliki zrodlowe Linuxa:
    2. W. Richard Stevens: "Programowanie zastosowan sieciowych w systemie Unix"
    3. M. Gabassi, B. Dupouy: "Przetwarzanie rozproszone w systemie UNIX"
    4. Dokumentacja Linuxa 2.0.0 - strony man oraz pliki info
    5. Vic Metcalfe, Andrew Gierth: "Programming sockets in C - FAQ"


    Autor: Piotr Walczuk