getaddrinfo(3)

NAZWA
getaddrinfo, freeaddrinfo, gai_strerror - tłumaczy adresy i usługi sieciowe

BIBLIOTEKA
Standardowa biblioteka C (libc, -lc)

SKŁADNIA
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *restrict node,
                const char *restrict service,
                const struct addrinfo *restrict hints,
                struct addrinfo **restrict res);

void freeaddrinfo(struct addrinfo *res);

const char *gai_strerror(int errcode);

    Wymagane ustawienia makr biblioteki glibc (patrz feature_test_macros(7)):

getaddrinfo(), freeaddrinfo(), gai_strerror():
    Od glibc 2.22:
        _POSIX_C_SOURCE >= 200112L
    glibc 2.21 i wcześniejsze:
        _POSIX_C_SOURCE

OPIS
Dla danego węzła node i usługi service, które identyfikują stację i usługę internetową, getaddrinfo() zwraca jedną lub więcej struktur addrinfo, każda z których zawiera adres internetowy, który można podać w wywołaniu do bind(2) lub connect(2). Funkcja getaddrinfo() łączy funkcjonalność udostępnianą przez funkcje gethostbyname(3) i getservbyname(3) w jeden interfejs, lecz w przeciwieństwie do nich, getaddrinfo() jest wielobieżna i umożliwia programom wyeliminowanie zależności od IPv4 lub IPv6.

Struktura addrinfo używana przez getaddrinfo() zawiera następujące pola:

    struct addrinfo {
        int              ai_flags;
        int              ai_family;
        int              ai_socktype;
        int              ai_protocol;
        socklen_t        ai_addrlen;
        struct sockaddr *ai_addr;
        char            *ai_canonname;
        struct addrinfo *ai_next;
    };

Argument hints wskazuje na strukturę addrinfo, która określa kryteria wyboru struktur adresów gniazd zwracanych w liście wskazywanej przez res. Jeśli hints nie wynosi NULL, wskazuje na strukturę addrinfo, której pola ai_family, ai_socktype i ai_protocol określają kryteria ograniczające zbiory adresów gniazd zwracane przez getaddrinfo(), zgodnie z poniższym opisem:

ai_family
    Pole określa żądaną rodzinę adresów wobec zwracanych adresów. Prawidłowe wartości tego pola obejmują AF_INET i AF_INET6. Wartość AF_UNSPEC wskazuje, że getaddrinfo() powinno zwrócić adresy gniazd dla dowolnej rodziny adresów (np. IPv4 lub IPv6), których można użyć z node i service.

ai_socktype
    Pole określa preferowany typ gniazda np. SOCK_STREAM lub SOCK_DGRAM. Podanie w tym polu wartości 0 wskazuje, że getaddrinfo() może zwrócić adresy gniazd dowolnego typu.

ai_protocol
    Pole określa protokół zwracanych adresów gniazd. Podanie w tym polu wartości 0 wskazuje, że getaddrinfo() może zwrócić adresy gniazd dowolnego protokołu.

ai_flags
    Pole określa dodatkowe opcje, opisane poniżej. Można podać wiele flag, sumując je bitowo (OR).

Wszystkie pozostałe pola w strukturze na którą wskazuje hints, muszą zawierać albo 0, albo pusty wskaźnik (w zależności od pola).

Podanie hints jako NULL jest równoważne ustawieniu ai_socktype i ai_protocol na 0; ai_family na AF_UNSPEC; a ai_flags na (AI_V4MAPPED | AI_ADDRCONFIG) (POSIX określa inną wartość domyślną dla ai_flags; zob. UWAGI). node zawiera albo adres sieciowy w postaci numerycznej (dla IPv4: w formacie liczb i kropek takim, jak obsługiwany przez inet_aton(3); dla IPv6: w formacie szesnastkowego łańcucha takim, jak obsługiwany przez init_pton(3)), albo nazwę stacji, dla której adresy sieciowe będą poszukiwane i rozwiązane. Jeśli hints.ai_flags zawiera znacznik AI_NUMERICHOST, to node musi być adresem sieciowym w postaci numerycznej. Znacznik AI_NUMERICHOST eliminuje jakiekolwiek, potencjalnie długotrwałe, poszukiwania adresu sieciowego stacji.

Jeśli w hints.ai_flags podano znacznik AI_PASSIVE, a node wynosi NULL, to zwracane adresy gniazd będą odpowiednie do przypisywania (bind(2)) gniazd, które będą akceptowały (accept(2)) połączenia. Zwracany adres gniazda będzie zawierał „adres wieloznaczny” („wildcard address”; INADDR_ANY w przypadku adresów IPv4, IN6ADDR_ANY_INIT w przypadku adresów IPv6). Adres wieloznaczny jest używany przez aplikacje (zwykle serwery), które mają zamiar akceptować połączenia na dowolnym z adresów sieciowych stacji. Jeśli node nie wynosi NULL, to znacznik AI_PASSIVE jest ignorowany.

Jeśli w hints.ai_flags nie podano znacznika AI_PASSIVE, to zwracane adresy gniazd będą odpowiednie do korzystania z connect(2), sendto(2) lub sendmsg(2). Jeśli node wynosi NULL, to adres sieciowy będzie ustawiony na adres interfejsu pętli zwrotnej (INADDR_LOOPBACK w przypadku adresów IPv4, IN6ADDR_LOOPBACK_INIT w przypadku adresów IPv6); jest to używane przez aplikacje, które mają zamiar komunikować się z innymi aplikacjami działającymi na tej samej stacji.

service ustawia port w każdej zwracanej strukturze adresu. Jeśli argument ten jest nazwą usługi (zob. services(5)), to jest tłumaczona na odpowiedni numer portu. Argument ten może być też liczbą dziesiętną, wówczas jest jedynie przekształcana na liczbę binarną. Jeśli service wynosi NULL, to numer portu zwracanych adresów gniazd będzie pozostawiony niezainicjowany. Jeśli w hints.ai_flags podano AI_NUMERICSERV, a service nie wynosi NULL, to service musi wskazywać na łańcuch zawierający numeryczny numer portu. Znacznik ten jest używany do powstrzymania rozwiązywania nazw w przypadkach, w których wiadomo, że nie będzie to konieczne.

Parametry node i service mogą być równe NULL, ale nie oba naraz.

Funkcja getaddrinfo() alokuje i inicjuje podlinkowaną listę struktur addrinfo, po jednej dla każdego adresu sieciowego, który pasuje do node i service, jest przedmiotem wszelkich ograniczeń nałożonych przez hints, a zwraca wskaźnik do początku listy w res. Pozycje na podlinkownej liście są linkowane za pomocą pola ai_next.

Istnieje wiele powodów, dla których podlinkowana lista może mieć więcej niż jedną strukturę addrinfo m.in.: stacja sieciowa może być wieloadresowa, dostępna za pomocą różnych protokołów (np. AF_INET oraz AF_INET6); albo ta sama usługa jest dostępna za pomocą różnych typów gniazd (np. jeden adres SOCK_STREAM i inny adres SOCK_DGRAM). Aplikacja zwykle spróbuje użyć adresy w zwracanej kolejności. Funkcja sortowania używana w getaddrinfo() jest zdefiniowana w RFC 3484; kolejność można dostosować dla danego systemu, edytując plik /etc/gai.conf (dostępny od glibc 2.5).

Jeśli hints.ai_flags zawiera znacznik AI_CANONNAME, to pole ai_canonname w pierwszej ze struktur addrinfo w zwracanej liście, jest ustawiane na oficjalną nazwę stacji.

Pozostałe pola w każdej ze zwracanych struktur addrinfo są inicjowane w następujący sposób:

•   Pola ai_family, ai_socktype i ai_protocol zwracają parametry tworzenia gniazd (tzn. pola te mają takie samo znaczenie, jak odpowiadające im argumenty socket(2)). Przykładowo ai_family może zwrócić AF_INET lub AF_INET6; ai_socktype może zwrócić SOCK_DGRAM lub SOCK_STREAM; a ai_protocol zwróci protokół gniazda.

•   Wskaźnik do adresu gniazda jest umieszczany w polu ai_addr, a rozmiar adresu gniazda w bajtach, jest umieszczana w polu ai_addrlen.

Jeśli hints.ai_flags zawiera znacznik AI_ADDRCONFIG, to adresy IPv4 są zwracane w liście, na którą wskazuje res tylko, gdy lokalny system ma skonfigurowany przynajmniej jeden adres IPv4, a adresy IPv6 są zwracane tylko, gdy lokalny system ma skonfigurowany przynajmniej jeden adres IPv6. Adres pętli zwrotnej nie jest w tym przypadku uważany za prawidłowy skonfigurowany adres. Znacznik ten jest przydatny np. w systemach korzystających wyłącznie z IPv4 aby zapewnić, że getaddrinfo() nie zwróci adresów gniazd IPv6, które zawsze zawiodłyby w connect(2) lub bind(2).

Jeśli hints.ai_flags określa znacznik AI_V4MAPPED, a hints.ai_family podano jako AF_INET6 oraz nie da się znaleźć pasującego adresu IPv6, to w liście, na którą wskazuje res, zwracane są adresy IPv4 zmapowane jako IPv6. Jeśli w hints.ai_flags podano AI_V4MAPPED oraz AI_ALL, to zwracane są adresy IPv6 oraz adresy IPv4 zmapowane jako IPv6. AI_ALL jest ignorowane, jeśli nie podano również AI_V4MAPPED.

Funkcja freeaddrinfo() zwalnia pamięć przydzieloną dla dynamicznie zaalokowanej listy res.

  Rozszerzenia getaddrinfo() dla domen ze znakami spoza ASCII (IDN)
Od glibc 2.3.4, getaddrinfo() rozszerzono w celu umożliwiania wybiórczego i przezroczystego konwertowania przychodzących i wychodzących nazw stacji z i na format Internationalized Domain Name (IDN; zob. RFC 3490, Internationalizing Domain Names in Applications (IDNA)). Zdefiniowano cztery nowe znaczniki:

AI_IDN
    Jeśli znacznik jest podany, nazwa węzła podana w node jest konwertowana na format IDN, jeśli to konieczne. Kodowanie źródłowe jest takie, jak w bieżących ustawieniach regionalnych (locale).

    Jeśli nazwa wejściowa zawiera znaki spoza ASCII, to używane jest kodowanie IDN. Te fragmenty nazwy węzła (oddzielone kropką), które zawierają znaki spoza ASCII są kodowane za pomocą ASCII Compatible Encoding, przed ich przekazaniem do funkcji rozwiązywania nazw.

AI_CANONIDN
    Po pomyślnym wyszukaniu nazwy, jeśli podano AI_CANONNAME, getaddrinfo() zwróci kanoniczną nazwę węzła odnoszącą się do wartości struktury addrinfo przekazywanej zwrotnie. Zwracana wartość jest dokładną kopią wartości zwracanej przez funkcję rozwiązywania nazw.

    Jeśli nazwa jest zakodowana za pomocą ACE, to będzie zawierać przedrostek xn-- w jednej lub więcej składowych nazw. Aby przekształcić te składowe w czytelną postać, oprócz znacznika AI_CANONNAME można podać też AI_CANONIDN. Wynikowy łańcuch będzie kodowany przy użyciu kodowania z bieżących ustawieniach regionalnych (locale).

AI_IDN_ALLOW_UNASSIGNED
AI_IDN_USE_STD3_ASCII_RULES
    Ustawienie tych znaczników włączy znaczniki, odpowiednio, IDNA_ALLOW_UNASSIGNED (zezwala na nieprzypisane kody Unikodu) i IDNA_USE_STD3_ASCII_RULES (sprawdza wyjście, aby upewnić się że jest to nazwa stacji zgodna z STD3) do użycia w obsłudze IDNA.

WARTOŚĆ ZWRACANA
getaddrinfo() zwraca 0, gdy zakończy się pomyślnie, a w przeciwnym razie jeden z następujących niezerowych kodów błędów:

EAI_ADDRFAMILY
    Podana stacja nie posiada żadnego adresu sieciowego dla zadanej rodziny adresów.

EAI_AGAIN
    Serwer nazw zwrócił błąd tymczasowy. Należy spróbować później.

EAI_BADFLAGS
    hints.ai_flags zawiera nieprawidłowe znaczniki; lub hints.ai_flags zawierało AI_CANONNAME i node wynosiło NULL.

EAI_FAIL
    Serwer nazw zwrócił błąd trwały.

EAI_FAMILY
    Żądana rodzina adresów nie jest obsługiwana.

EAI_MEMORY
    Brak pamięci.

EAI_NODATA
    Podana stacja sieciowa istnieje, ale nie zdefiniowano dla niej żadnego adresu sieciowego.

EAI_NONAME
    node lub service nie jest znane; albo zarówno node jak i service wynoszą NULL; albo w hints.ai_flags podano AI_NUMERICSERV, a service nie było numerycznym łańcuchem numeru portu.

EAI_SERVICE
    Żądana usługa nie jest dostępna dla żądanego typu portu. Może być dostępna za pomocą innego typu portu. Ten błąd może wystąpić na przykład, gdy jako service podano „shell” (usługa dostępna tylko na gniazdach strumieniowych), a hints.ai_protocol wynosiło IPPROTO_UDP albo hints.ai_socktype wynosiło SOCK_DGRAM; albo błąd może wystąpić gdy service nie wynosiło NULL, a hints.ai_socktype wynosiło SOCK_RAW (typ gniazda, w ogóle nie obsługujący koncepcji usług).

EAI_SOCKTYPE
    Żądany typ gniazda nie jest obsługiwany. Może się to zdarzyć na przykład, gdy hints.ai_socktype i hints.ai_protocol są niespójne (np. wynoszą, odpowiednio, SOCK_DGRAM i IPPROTO_TCP).

EAI_SYSTEM
    Inny błąd systemowy; ustawiane jest errno aby wskazać błąd.

Funkcja gai_strerror() tłumaczy te kody błędów na czytelny dla człowieka łańcuch, odpowiedni do zgłaszania błędów.

PLIKI
/etc/gai.conf

ATRYBUTY
Informacje o pojęciach używanych w tym rozdziale można znaleźć w podręczniku attributes(7).
Interfejs | Atrybut | Wartość
getaddrinfo() | Bezpieczeństwo wątkowe | MT-bezpieczne env locale
freeaddrinfo(), gai_strerror() | Bezpieczeństwo wątkowe | MT-bezpieczne

WERSJE
Zgodnie z POSIX.1, określenie hints jako NULL, powinno powodować przyjęcie ai_flags jako 0. Biblioteka GNU C zamiast tego przyjmuje w tym przypadku wartość (AI_V4MAPPED | AI_ADDRCONFIG), ponieważ wartość ta jest uważana za lepszą od przewidzianej normą.

STANDARDY
POSIX.1-2008.

getaddrinfo()
    RFC 2553.

HISTORIA
POSIX.1-2001.

AI_ADDRCONFIG
AI_ALL
AI_V4MAPPED
    glibc 2.3.3.

AI_NUMERICSERV
    glibc 2.3.4.

UWAGI
getaddrinfo() obsługuje notację adres%identyfikator-zasięgu do określenia identyfikatora zasięgu IPv6.

PRZYKŁADY
Poniższe programy demonstrują użycie getaddrinfo(), gai_strerror(), freeaddrinfo() i getnameinfo(3). Programy są serwerem i klientem typu echo dla datagramów UDP.

  Program serwera
    #include <netdb.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <unistd.h>

    #define BUF_SIZE 500

    int
    main(int argc, char *argv[])
    {
        int                      sfd, s;
        char                     buf[BUF_SIZE];
        ssize_t                  nread;
        socklen_t                peer_addrlen;
        struct addrinfo          hints;
        struct addrinfo          *result, *rp;
        struct sockaddr_storage  peer_addr;

        if (argc != 2) {
            fprintf(stderr, "Użycie: %s port\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;    /* Zezwala na IPv4 lub IPv6 */
        hints.ai_socktype = SOCK_DGRAM; /* Gniazdo datagramowe */
        hints.ai_flags = AI_PASSIVE;    /* Dla wieloznacznego adresu IP */
        hints.ai_protocol = 0;          /* Dowolny protokół */
        hints.ai_canonname = NULL;
        hints.ai_addr = NULL;
        hints.ai_next = NULL;

        s = getaddrinfo(NULL, argv[1], &hints, &result);
        if (s != 0) {
            fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
            exit(EXIT_FAILURE);
        }

        /* getaddrinfo() zwraca listę struktur adresów. Próbowany
           jest każdy adres do momentu pomyślnego bind(2).
           Jeśli socket(2) (lub bind(2)) zawiedzie, (gniazdo jest
           jest zamykane i) próbowany jest następny adres.  */

        for (rp = result; rp != NULL; rp = rp->ai_next) {
            sfd = socket(rp->ai_family, rp->ai_socktype,
                         rp->ai_protocol);
            if (sfd == -1)
                continue;

            if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
                break;                  /* Powodzenie */

            close(sfd);
        }

        freeaddrinfo(result);           /* Nie jest już potrzebne */

        if (rp == NULL) {               /* Nie udało się z żadnym adresem */
            fprintf(stderr, "Przypisanie nie powiodło się\n");
            exit(EXIT_FAILURE);
        }

        /* Odczytuje datagramy i odsyła je wysyłającemu.  */

        for (;;) {
            char host[NI_MAXHOST], service[NI_MAXSERV];

            peer_addrlen = sizeof(peer_addr);
            nread = recvfrom(sfd, buf, BUF_SIZE, 0,
                             (struct sockaddr *) &peer_addr, &peer_addrlen);
            if (nread == -1)
                continue;               /* Ignoruje niepomyślne żądanie */

            s = getnameinfo((struct sockaddr *) &peer_addr,
                            peer_addrlen, host, NI_MAXHOST,
                            service, NI_MAXSERV, NI_NUMERICSERV);
            if (s == 0)
                printf("Otrzymano %zd bajtów z %s:%s\n",
                       nread, host, service);
            else
                fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));

            if (sendto(sfd, buf, nread, 0, (struct sockaddr *) &peer_addr,
                       peer_addrlen) != nread)
            {
                fprintf(stderr, "Błąd przy wysyłaniu odpowiedzi\n");
            }
        }
    }

  Program klienta
    #include <netdb.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <unistd.h>

    #define BUF_SIZE 500

    int
    main(int argc, char *argv[])
    {
        int              sfd, s;
        char             buf[BUF_SIZE];
        size_t           size;
        ssize_t          nread;
        struct addrinfo  hints;
        struct addrinfo  *result, *rp;

        if (argc < 3) {
            fprintf(stderr, "Użycie: %s stacja port komunik...\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        /* Pozyskuje adres(y) pasującej stacji/portu. */

        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;    /* Zezwala na IPv4 lub IPv6 */
        hints.ai_socktype = SOCK_DGRAM; /* Gniazdo datagramowe */
        hints.ai_flags = 0;
        hints.ai_protocol = 0;          /* Dowolny protokół */

        s = getaddrinfo(argv[1], argv[2], &hints, &result);
        if (s != 0) {
            fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
            exit(EXIT_FAILURE);
        }

        /* getaddrinfo() zwraca listę struktur adresów. Próbuje
           każdego adresu, do pomyślnego połączenia (connect(2)).
           Jeśli socket(2) (lub connect(2)) zawiedzie, (zamykamy
           gniazdo i) próbujemy następny adres.  */

        for (rp = result; rp != NULL; rp = rp->ai_next) {
            sfd = socket(rp->ai_family, rp->ai_socktype,
                         rp->ai_protocol);
            if (sfd == -1)
                continue;

            if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
                break;                  /* Powodzenie */

            close(sfd);
        }

        freeaddrinfo(result);           /* Nie jest już potrzebne */

        if (rp == NULL) {               /* Nie udało się z żadnym adresem */
            fprintf(stderr, "Nie udało się połączyć\n");
            exit(EXIT_FAILURE);
        }

        /* Wysyła pozostałe argumenty wiersza poleceń jako oddzielne
           datagramy i odczytuje odpowiedzi z serwera.  */

        for (size_t j = 3; j < argc; j++) {
            size = strlen(argv[j]) + 1;
                    /* +1 dla końcowego bajtu null */

            if (size > BUF_SIZE) {
                fprintf(stderr,
                    "Ignorowanie długiego komunikatu w argumencie %zu\n", j);
                continue;
            }

            if (write(sfd, argv[j], size) != size) {
                fprintf(stderr, "częściowy/nieudany zapis\n");
                exit(EXIT_FAILURE);
            }

            nread = read(sfd, buf, BUF_SIZE);
            if (nread == -1) {
                perror("read");
                exit(EXIT_FAILURE);
            }

            printf("Otrzymano %zd bajtów: %s\n", nread, buf);
        }

        exit(EXIT_SUCCESS);
    }

ZOBACZ TAKŻE
getaddrinfo_a(3), gethostbyname(3), getnameinfo(3), inet(3), gai.conf(5), hostname(7), ip(7)

TŁUMACZENIE
Tłumaczenie niniejszej strony podręcznika: Andrzej Krzysztofowicz <ankry@green.mf.pg.gda.pl> i Michał Kułach <michal.kulach@gmail.com>

Niniejsze tłumaczenie jest wolną dokumentacją. Bliższe informacje o warunkach licencji można uzyskać zapoznając się z GNU General Public License w wersji 3 (https://www.gnu.org/licenses/gpl-3.0.html) lub nowszej. Nie przyjmuje się ŻADNEJ ODPOWIEDZIALNOŚCI.

Błędy w tłumaczeniu strony podręcznika prosimy zgłaszać na adres listy dyskusyjnej manpages-pl-list@lists.sourceforge.net.