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:
#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.
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);
Standard POSIX wprowadza funkcję getaddrinfo
, która pozwala na bezpieczne
tłumaczenie nazwy domenowej (lub adresu IP) z tekstu na odpowiedni format dla
funkcji sieciowych (wskaźnik na strukturę sockaddr
). Funkcja działa zarówno
IPv4 i IPv6.
getaddrinfo
przygotowuje wszystkie argumenty potrzebne funkcjom takim jak
socket
, connect
czy bind
.
Funkcja getaddrinfo
sama alokuje pamięć dla wyników, stąd wyniki muszą być
zwolnione ręcznie funkcją freeaddrinfo
.
Standard POSIX wprowadza też m. inn. wygodną funkcję do tłumaczenia sockaddr*
na tekst – funkcję getnameinfo
.
Składnia getaddrinfo
:
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; // pole flag, używane jeśli struktura ma być podpowiedziami dla getaddrinfo – patrz manual 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; // wypełniane (tylko jeśli podano odpowiednią flagę) nazwą o którą zapytano struct addrinfo *ai_next; // struktura addrinfo tworzy jednokierunkową linked listę, ai_next wskazuje na jej następny element };
Składnia getnameinfo
:
int getnameinfo( // zwraca 0 (ok) lub niezerowy kod błędu (można go przerobić na tekst używając gai_strerror) const struct sockaddr *addr, // adres (IPv4 lub IPv6) do przerobienia na tekst socklen_t addrlen, // ilość bajtów powyższego adresu // char * host, // wskaźnik na bufor do którego zostanie wpisany adres IP (lub nazwa domenowa) jako tekst size_t hostLen, // ilość miejsca w powyższym buforze // (dla zmiany IP na tekst powinien wystarczyć bufor o długości NI_MAXHOST) char * port, // wskaźnik na bufor do którego zostanie wpisany numer (lub nazwa) portu jako tekst size_t portLen, // ilość miejsca w powyższym buforze // (dla zmiany portu na tekst powinien wystarczyć bufor o długości NI_MAXSERV) int flags); // pole flag, pozwalające wybrać m. inn. czy IP i port mają być tłumaczone na numery czy nazwy
Pełen opis funkcji i struktur znajdziesz na stronie manuala do getaddrinfo (man getaddrinfo
)
lub w standardzie POSIX ([1],
[2],
[3]).
Uwaga: getaddrinfo
i getnameinfo
w razie sukcesu zwraca 0
, a w
przypadku błędu zwraca jego kod. Czytelny dla człowieka komunikat powiązany z
kodem błędu można uzyskać funckją gai_strerror
.
Zwróć uwagę, że to odbiega od typowej konwencji POSIX.
Przykłady użycia:
#include <netdb.h> #include <cstdio> #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) {fprintf(stderr, "Getaddrinfo failed: %s\n", gai_strerror(res)); return 1;} 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; }
#include <netdb.h> #include <cstdio> int main(){ addrinfo * resolved; // Podpowiedzi, port i nazwa hosta są opcjonalne. int res = getaddrinfo("ietf.org", nullptr, nullptr, &resolved); if(res) {fprintf(stderr, "Getaddrinfo failed: %s\n", gai_strerror(res)); return 1;} 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) {fprintf(stderr, "Getnameinfo failed: %s\n", gai_strerror(res)); return 1;} 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:
getaddrinfo
z podpowiedzią żądającą protokołu TCPgetaddrinfo
getaddrinfo
W tym zadaniu wszystkie argumenty funkcji
socket
i wszystkie poza deskryptorem
gniazda argumenty funkcji connect
mają być brane z wyników funkcji getaddrinfo
Zadanie 3 Zmień kod ostatniego zadania z poprzedniego tematu (serwer UDP wysyłający każdą wiadomość do wszystkich od których wcześniej coś dostał) tak, by serwer w treść każdej wiadomości wstawiał informację skąd przyszła. Tłumacz adres IP na nazwę domenową, ale numer portu pozostaw jako liczbę.