Narzędzia użytkownika

Narzędzia witryny


Pasek boczny

sk2:sockets_netdbs

Zmiana adresu domenowego na IP

Tradycyjna funkcja gethostbyname

Najstarsze funkcje do zmiany to gethostbyname i gethostbyaddr. Funkcje są przestarzałe i powszechnie używane. Problemy z ich działaniem obrazuje poniższy kod:

ghbn.cpp
#include <unistd.h>
#include <thread>
#include <arpa/inet.h>
#include <netdb.h>
#include <cstdio>
 
int main() {
 
    std::thread t1([]{
        sleep(1);
        gethostbyname("spam.org");
    });
 
    std::thread t2([]{
        hostent* ret = gethostbyname("cat.put.poznan.pl");
        sleep(2);
        printf("%s: %s\n", ret->h_name, inet_ntoa(**(in_addr**)ret->h_addr_list));
    });
 
    t1.join();
    t2.join();    
    return 0;
}

Zadanie 1. Przetestuj powyższy kod. Zastanów się jak uniknąć problemu, który pokazuje powyższy kod.

Rozszerzenia GNU

Kompilator GCC udostępnia wiele rozszerzeń "łatających" braki standardów https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html.
Towarzysząca mu biblioteka glibc wprowadza między innymi funkcję gethostbyname_r (funkcje *_r są reentrant, tj. można je łatwiej stosować w aplikacjach wielowątkowych https://en.wikipedia.org/wiki/Reentrancy_%28computing%29). gethostbyname_r może być używana współbieżnie.

hostent he, *resptr;
int status, retval;
char buffer[4096];
retval = gethostbyname_r("cat.put.poznan.pl", &he, buffer, sizeof(buffer), &resptr, &status);
if(retval)
    error(1,0,"gethostbyname_r error: %s", hstrerror(status));
if(!resptr)
    error(1,0,"empty result");
print(resptr);

getaddrinfo

Standard POSIX wprowadza funkcje getaddrinfo, która pozwala na bezpieczne tłumaczenie nazwy domenowej na adresy IP (zarówno IPv4 i IPv6). Funkcja od razu tworzy gotową strukturę sockaddr. Wynik musi być zwolniony przez programistę funkcją freeaddrinfo. Poza getaddrinfo standard wprowadza też m. inn. funkcję do tłumaczenia sockaddr* na tekst – getnameinfo.

Składnia:

int getaddrinfo(                  // zwraca 0 (ok) lub niezerowy kod błędu (można go przerobić na tekst używając gai_strerror)
    const char * ip_or_hostname,  // nazwa domenowa lub adres IP 
    const char * port_or_service, // numer portu (np. "80") lub nazwa usługi (np. "http", ustawi port na 80)
    const struct addrinfo *hints, // pozwala wybrać typ adresu, wyłączyć używanie DNS, etc.
    struct addrinfo **res);       // pole na wynik (jeśli puste - nic nie znaleziono)

Definicja struktury addrinfo:

struct addrinfo {
    int ai_flags;
    int ai_family;    // ai_family, ai_socktype i ai_protocol mają znaczenie identyczne jak
    int ai_socktype;  // w funkcji socket(). Mogą być ustawione w podpowiedziach na wybraną
    int ai_protocol;  // wartość lub na 0 (wtedy oznaczają wszystkie pasujące wartości)
    socklen_t ai_addrlen;          // ai_addr to adres o długości ai_addrlen który może być
    struct sockaddr *ai_addr;      // użyty w funkcji connect lub bind
    char *ai_canonname;
    struct addrinfo *ai_next;  // struktura addrinfo tworzy jednokierunkową linked listę, ai_next wskazuje na jej następny element 
};

Pełen opis funkcji getaddrinfo i struktury addrinfo znajdziesz na stronie manuala do getaddrinfo (man getaddrinfo) lub w standardzie POSIX ([1], [2], [3]).
Uwaga: getaddrinfo w razie sukcesu zwraca 0, a w przypadku błędu zwraca jego kod.

Przykłady użycia:

gai1.cpp
#include <netdb.h>
#include <cstdio>
#include <error.h>  // niestandardowa funkcja ułatwiająca obsługę błędów (dostępna w glibc)
#include <arpa/inet.h>
 
int main(){
    // Ustawienie "podpowiedzi" - sterowanie jakie wyniki chcemy otrzymać:
    addrinfo hints {};    // uwaga: puste 'list initialization' zeruje całą strukturę
    hints.ai_family   = AF_INET;         // tylko IPv4 (AF_INET)
    hints.ai_protocol = IPPROTO_UDP;     // protokół UDP
 
    // Zmienna w której będzie umieszczona lokalizacja wyniku w pamięci
    addrinfo * resolved;
 
    // Z sieci bezprzewodowej PP (lub innych używających serwerów nazw z podsieci 150.254.5.0/24)
    // proszę zmienić "pool.ntp.org" na "onet.pl" - Dział Obsługi i Eksploatacji z niezrozumiałych
    // przyczyn cenzuruje odpowiedzi z DNS 
    int res = getaddrinfo("pool.ntp.org", "ntp", &hints, &resolved);
 
    if(res) error(1,0,"Getaddrinfo failed: %s\n", gai_strerror(res));
    if(!resolved) error(1,0,"Empty result\n");
 
    for(addrinfo * it = resolved; it; it=it->ai_next){
        sockaddr_in* addr = (sockaddr_in*) it->ai_addr; // <- rzutowanie bezpieczne,
        printf(" %s\n",inet_ntoa(addr->sin_addr));      //    bo w podpowiedziach 
    }                                                   //    zażądaliśmy adresów IPv4
 
    freeaddrinfo(resolved);
 
    return 0;
}
gai2.cpp
#include <netdb.h>
#include <cstdio>
#include <error.h>
 
int main(){
    addrinfo * resolved;
    // Podpowiedzi, port i nazwa hosta są opcjonalne.
    int res = getaddrinfo("ietf.org", nullptr, nullptr, &resolved);
 
    if(res) error(1,0,"Getaddrinfo failed: %s\n", gai_strerror(res));
    if(!resolved) error(1,0,"Empty result\n");
 
    char ip[40]; // maks. długość IP(v6) jako tekst: 8 bloków po 4 znaki oddzielone ':'
                 // można użyć stałą NI_MAXHOST określającą maksymalną długość nazwy domenowej 
    for(addrinfo * it = resolved; it; it=it->ai_next){
        // funkcja getnameinfo tłumaczy dowolny sockaddr na tekst
        res = getnameinfo(it->ai_addr, it->ai_addrlen, ip, 40, nullptr, 0, NI_NUMERICHOST);
        if(res) error(1,0,"Getnameinfo failed: %s\n", gai_strerror(res));
        else printf("%40s (socktype: %d, proto: %d)\n", ip, it->ai_socktype, it->ai_protocol);
    }
 
    freeaddrinfo(resolved);
 
    return 0;
}

Zadanie 2. Stwórz klienta TCP, który:

  • odczyta z listy argumentów adres i numer portu
  • użyje funkcji getaddrinfo z podpowiedzią żądającą protokołu TCP
  • utworzy socket korzystając z rodziny adresów zwróconej przez getaddrinfo
  • połączy się funkcją connect korzystając ze struktury sockaddr i informacji o jej długości zwróconej przez getaddrinfo
sk2/sockets_netdbs.txt · ostatnio zmienione: 2023/10/17 14:03 przez jkonczak