====== 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: #include #include #include #include #include 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 [[https://www.gnu.org/software/libc/|glibc]] wprowadza między innymi funkcję ''[[https://www.gnu.org/software/libc/manual/html_node/Host-Names.html#index-gethostbyname_005fr|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 ([[https://pubs.opengroup.org/onlinepubs/9699919799/functions/freeaddrinfo.html|[1]]], [[https://pubs.opengroup.org/onlinepubs/9699919799/functions/getnameinfo.html|[2]]], [[https://pubs.opengroup.org/onlinepubs/9699919799/functions/gai_strerror.html|[3]]]). \\ Uwaga: ''getaddrinfo'' w razie sukcesu zwraca ''0'', a w przypadku błędu zwraca jego kod. Przykłady użycia: #include #include #include // niestandardowa funkcja ułatwiająca obsługę błędów (dostępna w glibc) #include 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; } #include #include #include 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''