przejście do zawartości
Jan Kończak
Narzędzia użytkownika
Zaloguj
Narzędzia witryny
Narzędzia
Pokaż stronę
Poprzednie wersje
Odnośniki
Ostatnie zmiany
Menadżer multimediów
Indeks
Zaloguj
Ostatnie zmiany
Menadżer multimediów
Indeks
Jesteś tutaj:
start
»
bio-psiec
»
ghbn_gai_gni
bio-psiec:ghbn_gai_gni
Ta strona jest tylko do odczytu. Możesz wyświetlić źródła tej strony ale nie możesz ich zmienić.
===== DNS - przypomnienie ===== Omawiany wcześniej na zajęciach [[bio-sk:dns|system nazw domenowych]] pozwala zmieniać nazwy domenowe na adresy IP oraz adresy IP na nazwy domenowe. Na zajęciach o [[bio-psiec:netcat_netstat#znane_numery_portow|protokołach warstwy transportu]] pojawiła się informacja że znane numery portów mają swoje nazwy, w Linuksie źródłem informacji o nazwanych portach jest plik ''/etc/services''. ~~Zadanie.#~~ Przypomnij sobie z zajęć z DNS ile nazw domenowych może rozwiązywać się na ten sam adres IP oraz do ilu nazw domenowych może prowadzić odwzorowanie odwrotne z adresu IP. ~~Zadanie.#~~ Używając programu ''netcat'' lub ''socat'' nawiąż połączenie TCP podając jako adres ''antares.put.poznan.pl'', a jako numer portu podaj ciąg znaków ''smtp''. \\ Następnie sprawdź jak to połączenie prezentowane jest w programie ''netstat'' z przełącznikami ''-ptW'' oraz ''-ptW -n'', lub programie ''ss'' z przełącznikami ''-pt -r'' oraz ''-pt -n''. <small> (W programie ''ss'' możesz dodać też do argumentów ''dst antares.put.poznan.pl'' żeby zobaczyć tylko połączenia kierowane pod ten adres.) </small> ~~Zadanie.#~~ Powtórz poprzednie zadanie, ale łącz się pod adres ''put.poznan.pl'' i port ''www''. \\ Jaką różnicę widać w prezentowaniu informacji o połączeniu w porównaniu z poprzednim zadaniem? ===== Przestarzałe funkcje ===== Najstarsza funkcja do znalezienia IP dla podanej nazwy domenowej to ''gethostbyname''. Ta funkcja jest przestarzała (i mimo to jeszcze używana). Przykład użycia: <html><div style=margin-top:-1.4em></div></html> <code cpp ghbn_example.cpp> #include <arpa/inet.h> #include <cstdio> #include <netdb.h> int main(int c, char **v) { const char * addrAsText = c < 2 ? "pool.ntp.org" : v[1]; hostent *ret = gethostbyname(addrAsText); if (!ret) { herror("gethostbyname failed"); return 1; } for (int i = 0; ret->h_addr_list[i]; ++i) { in_addr *addrAsInt = (in_addr *)ret->h_addr_list[i]; puts(inet_ntoa(*addrAsInt)); } return 0; } </code> ~~Zadanie.#~~ Używając powyższego kodu sprawdź adresy domen //put.poznan.pl//, //dnslabs.nl// i //ipv6.google.com//. Wejdź na te stony w przeglądarce. Których nazw domenowych funkcja ''gethostbyname'' nie rozwiązała na IP i dlaczego? Co ważne, funkcja ''gethostbyname'' nie może być bezpiecznie używana w kodzie wielowątkowym. ~~Zadanie.#~~ Sprawdź co się może wydarzyć jeśli ''gethostbyname'' zostanie użyta współbieżnie((ale nie równolegle, wtedy mogłoby być dużo gorzej)) uruchamiając poniższy kod. <html><div style=margin-top:-1.4em></div></html> <code cpp ghbn_and_threads.cpp> #include <arpa/inet.h> #include <cstdio> #include <netdb.h> #include <thread> #include <unistd.h> #define S usleep(100000); int main() { std::thread([] { S; gethostbyname((char[]){89, 84, 46, 66, 69, 0}); }).detach(); hostent *r = gethostbyname("cat.put.poznan.pl"); S; S; printf("%s: %s\n", r->h_name, inet_ntoa(*(in_addr *)r->h_addr_list[0])); return 0; } </code> <html><div style=margin-top:-1.4em></div></html> Wyjaśnij skąd biorą się wypisane na standardowe wyjście wyniki. <small> Poza ''gethostbyname'' istnieje funkcja ''gethostbyaddr'' zmieniająca IP na nazwę domenową. \\ Do zmiany nazwy portu na numer (lub na odwrót) istnieje funkcja ''getservbyname'' (i ''getservbyport''). </small> Wszystkie wspominane tutaj funkcje mają podobne problemy – między innymi brak wsparcia dla IPv6 i współbieżności. ===== getaddrinfo ===== Standard POSIX wprowadza funkcję ''getaddrinfo'', która pozwala na bezpieczne tłumaczenie nazwy domenowej (lub adresu IP) oraz nazwy portu (lub jego numeru) z tekstu na odpowiedni format dla funkcji sieciowych (czyli wskaźnik na strukturę ''sockaddr''). \\ Poza adresem potrzebnym np. w ''bind'' czy ''connect'', funkcjach ''getaddrinfo'' przygotowuje wszystkie argumenty potrzebne w funkcji ''socket''. Funkcja ''getaddrinfo'' sama alokuje pamięć dla wyników, stąd wyniki muszą być zwolnione ręcznie funkcją ''freeaddrinfo''. __Uwaga:__ ''getaddrinfo'' 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ć funkcją ''gai_strerror''. Zwróć uwagę, że to odbiega od typowej konwencji POSIX. Przykład użycia ''getaddrinfo'' wyświetlający informacje które znajdują się w zwracanych przez tą funkcję strukturach ''addrinfo'': <html><div style=margin-top:-1.4em></div></html> <code cpp gai_example.cpp> #include <iostream> #include <netdb.h> #include <string> #include <unordered_map> // poniższy blok kodu, do funkcji main, to pomocnicze struktury i funkcje; // proszę w trakcie zajęć nie poświęcać czasu na ich analizę! using namespace std; typedef unordered_map<int, string> map; map families{{AF_INET,"AF_INET "},{AF_INET6,"AF_INET6"}}; map socktypes{{SOCK_RAW,"SOCK_RAW "},{SOCK_STREAM,"SOCK_STREAM"},{SOCK_DGRAM,"SOCK_DGRAM "}}; map protocols{{0,"0"},{IPPROTO_TCP,"IPPROTO_TCP"},{IPPROTO_UDP,"IPPROTO_UDP"}}; string getIp(sockaddr*a,socklen_t l){char b[40];getnameinfo(a,l,b,40,0,0,NI_NUMERICHOST);return b;} string getPort(sockaddr*a,socklen_t l){char b[6];getnameinfo(a,l,0,0,b,6,NI_NUMERICSERV);return b;} int main(int c, char **a) { const char *host = (c<2 ? "ietf.org" : a[1]); const char *port = (c<3 ? nullptr : a[2]); addrinfo *resolved; int res = getaddrinfo(host, port, nullptr, &resolved); if (res) { cerr << "Getaddrinfo failed: " << gai_strerror(res) << endl; return 1; } for (addrinfo *it = resolved; it; it = it->ai_next) { cout << "family: " << families[ it->ai_family] << " address: " << getIp( it->ai_addr, it->ai_addrlen) << " port: " << getPort( it->ai_addr, it->ai_addrlen) << " socktype: " << socktypes[it->ai_socktype] << " protocol: " << protocols[it->ai_protocol] << endl; } freeaddrinfo(resolved); return 0; } </code> <html><div style=margin-top:-1.4em></div></html> ++++ Kod dla MS Windows | <html><div style=margin-top:-1.4em></div></html> <code cpp gai_example.cpp> #include <iostream> #include <string> #include <unordered_map> #include <ws2tcpip.h> // ┐ getaddrinfo jest #pragma comment(lib, "Ws2_32.lib") // ┘ w innej bibliotece // poniższy blok kodu, do funkcji main, to pomocnicze struktury i funkcje; // proszę w trakcie zajęć nie poświęcać czasu na ich analizę! using namespace std; typedef unordered_map<int, string> map; map families{{AF_INET,"AF_INET "},{AF_INET6,"AF_INET6"}}; map socktypes{{0,"0"},{SOCK_RAW,"SOCK_RAW "},{SOCK_STREAM,"SOCK_STREAM"},{SOCK_DGRAM,"SOCK_DGRAM "}}; map protocols{{0,"0"},{IPPROTO_TCP,"IPPROTO_TCP"},{IPPROTO_UDP,"IPPROTO_UDP"}}; string getIp(sockaddr*a,socklen_t l){char b[40];getnameinfo(a,l,b,40,0,0,NI_NUMERICHOST);return b;} string getPort(sockaddr*a,socklen_t l){char b[6];getnameinfo(a,l,0,0,b,6,NI_NUMERICSERV);return b;} int main(int c, char**a) { WSADATA wsadata{}; // ┐ no i standardowo trzeba WSAStartup(MAKEWORD(2, 2),&wsadata); // ┘ zainicjalizować winsock const char *host = (c<2 ? "ietf.org" : a[1]); const char *port = (c<3 ? nullptr : a[2]); addrinfo *resolved; int res = getaddrinfo(host, port, nullptr, &resolved); if (res) { cerr << "Getaddrinfo failed: " << gai_strerror(res) << endl; return 1; } for (addrinfo *it = resolved; it; it = it->ai_next) { cout << "family: " << families[ it->ai_family] << " address: " << getIp( it->ai_addr, it->ai_addrlen) << " port: " << getPort( it->ai_addr, it->ai_addrlen) << " socktype: " << socktypes[it->ai_socktype] << " protocol: " << protocols[it->ai_protocol] << endl; } freeaddrinfo(resolved); return 0; } </code> <html><div style=margin-top:-1.4em></div></html> Zwróć uwagę, że Windows nie ustawia ''.ai_socktype'' ani ''.ai_protocol'' jeżeli pasuje kilka wartości – w tym nie trzyma się [[https://pubs.opengroup.org/onlinepubs/9799919799/functions/freeaddrinfo.html|specyfikacji POSIX]]. ++++ Zwróć uwagę że w zwracanych przez ''getaddrinfo'' strukturach ''addrinfo'' pole ''.ai_addr'' jest typu ''sockaddr*'', więc przekazując adres do funkcji takich jak ''bind'' czy ''connect'' nie ma potrzeby rzutowania. Jednocześnie z takiego pola nie da się określić rozmiaru struktury, stąd w ''addrinfo'' jest też pole ''.ai_addrlen'' wypełniane rozmiarem adresu. ~~Zadanie.#~~ Przetestuj powyższy kod. Sprawdź go dla kilku wybranych domen i nazw portów. \\ Sprawdź między innymi nazwy portów ''bnetgame'', ''urd'' i ''who''. Trzecim argumentem funkcji ''getaddrinfo'' jest wskaźnik na wypełnioną przez programistę strukturę ''addrinfo'', która pozwala wybrać które z potencjalnych wyników mają zostać zwrócone. Działanie podpowiedzi jest zaprezentowane poniżej: <html><div style=margin-top:-1.4em></div></html> <html> <script> function genGaiCall(){ var hints = ""; if(document.getElementById("ai_family").value) hints += "hints.ai_family = " + document.getElementById("ai_family").value + ";\n"; if(document.getElementById("ai_socktype").value) hints += "hints.ai_socktype = " + document.getElementById("ai_socktype").value + ";\n"; if(document.getElementById("ai_protocol").value) hints += "hints.ai_protocol = " + document.getElementById("ai_protocol").value + ";\n"; var flags = ""; if(document.getElementById("AI_PASSIVE").checked) flags += "AI_PASSIVE | "; if(!document.getElementById("AI_NUMERICHOST").checked) flags += "AI_NUMERICHOST | "; if(!document.getElementById("AI_NUMERICSERV").checked) flags += "AI_NUMERICSERV | "; if(flags) hints += "hints.ai_flags = " + flags.slice(0, -3) + ";\n"; var text = ""; if(hints) text = "struct addrinfo hints = {};\n" + hints; text += "struct addrinfo *res;\n"; text += "int status;\n"; text += "if (0 != (status = getaddrinfo(address, port, " + (hints?"&hints":"NULL") + ", &res))) {\n"; text += " fprintf(stderr, \"Query failed: %s\\n\", gai_strerror(status));\n"; text += " …\n"; text += "}\n"; text += "// przykładowe użycie tylko pierwszego ze zwróconych adresów:\n"; text += "int sockFd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);\n"; if(document.getElementById("AI_PASSIVE").checked) text += "bind(sockFd, res->ai_addr, res->ai_addrlen);\n"; else text += "connect(sockFd, res->ai_addr, res->ai_addrlen);\n"; text += "freeaddrinfo(res);"; document.getElementById("gaiCall").innerHTML = text; } </script> <div style="padding: 0.5em; border: 2px lightgray solid; display: inline-block;"> <!--Interaktywny przykład jak używać <code>getaddrinfo</code>:--> <small> <div style="display:table"> <div style="display:table-row"> <div style="display:table-cell"> <form> Protokół warstwy sieci: <select onChange="genGaiCall();" id="ai_family"> <option value="">dowolny</option> <option value="AF_INET">IPv4</option> <option value="AF_INET6">IPv6</option> </select> <br> Rodzaj gniazda: <select onChange="genGaiCall();" id="ai_socktype"> <option value="">dowolny</option> <option value="SOCK_STREAM">strumieniowe</option> <option value="SOCK_DGRAM">datagramowe</option> </select> <br> Protokół warstwy transportowej: <select onChange="genGaiCall();" id="ai_protocol"> <option value="">dowolny</option> <option value="IPPROTO_TCP">TCP</option> <option value="IPPROTO_UDP">UDP</option> </select> <br> Opcje: <ul style="margin-top: 0em"> <li><input onChange="genGaiCall();" type="checkbox" id="AI_PASSIVE"><label class="li" for="AI_PASSIVE"> chcę użyć adresu w funkcji <code>bind</code></label></li> <li><input onChange="genGaiCall();" type="checkbox" checked id="AI_NUMERICHOST"><label class="li" for="AI_NUMERICHOST"> chcę użyć DNS do nazw domenowych</label></li> <li><input onChange="genGaiCall();" type="checkbox" checked id="AI_NUMERICSERV"><label class="li" for="AI_NUMERICSERV"> zamiast numeru portu akceptuj też nazwę</label></li> </ul> </form> </div> <div style="display:table-cell"> <pre id="gaiCall" style="margin-left:1em;margin-bottom:0;user-select:none;"> </pre> <script>genGaiCall();</script> </div> </div> </div> </small> </div> </html> ===== getnameinfo ===== Standard POSIX wprowadza też m. inn. wygodną funkcję do tłumaczenia ''sockaddr*'' na tekst – funkcję ''getnameinfo''. Działanie ''getnameinfo'' użyte w serwerze do tłumaczenia adresu z którego połączy się klient prezentuje poniższy kod: <html><div style=margin-top:-1.4em></div></html> <code cpp gni_example.cpp> #include <iostream> #include <netdb.h> #include <sstream> #include <unistd.h> int main(int c, char **a) { const char *port = (c < 2 ? "4444" : a[1]); int family = (c < 3 ? AF_INET6 : AF_INET), status, one = 1, zero = 0; addrinfo *res, hints{.ai_flags = AI_PASSIVE, .ai_family = family, .ai_protocol = IPPROTO_TCP}; if ((status = getaddrinfo(nullptr, port, &hints, &res))) { std::cerr << "Getaddrinfo failed: " << gai_strerror(status) << std::endl; return 1; } int s = socket(res->ai_family, res->ai_socktype, 0); setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); // setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)); if (bind(s, res->ai_addr, res->ai_addrlen)) { perror("Bind failed"); return 1; } freeaddrinfo(res); listen(s, 1); std::cout << "Listening on port \e[1;33m" << port << "\e[0m" << std::endl; while (1) { sockaddr *cliAddr = (sockaddr *)new sockaddr_storage; socklen_t cliAddrLen = sizeof(sockaddr_storage); int c = accept(s, cliAddr, &cliAddrLen); if (c == -1) { perror("Bind failed"); return 1; } std::stringstream msg; msg << "Client connected from:" << std::endl; char host[NI_MAXHOST], port[NI_MAXSERV]; status = getnameinfo(cliAddr, cliAddrLen, host, NI_MAXHOST, port, NI_MAXSERV, 0); if (status) std::cerr << "Getnameinfo failed: " << gai_strerror(status) << std::endl; msg << " [resolved] address: " << host << " port: " << port << std::endl; status = getnameinfo(cliAddr, cliAddrLen, host, NI_MAXHOST, port, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV); if (status) std::cerr << "Getnameinfo failed: " << gai_strerror(status) << std::endl; msg << " [numeric ] address: " << host << " port: " << port << std::endl; delete cliAddr; std::cout << msg.str() << std::flush; write(c, msg.str().c_str(), msg.str().length()); shutdown(c, SHUT_RDWR); close(c); } } </code> <html><div style=margin-top:-1.4em></div></html> ++++ Kod dla MS Windows | <html><div style=margin-top:-1.4em></div></html> <code cpp gni_example.cpp> #include <iostream> #include <sstream> #include <winsock2.h> // ┐ #include <ws2tcpip.h> // ├ inne pliku nagłówkowe dla winsock #pragma comment(lib, "Ws2_32.lib") // ┘ // pomocnicza funkcja dla prostszej obsługi błędów inline char *getLastErrorMessage() { static thread_local char msgBuf[1024]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, WSAGetLastError(), 0, msgBuf, 1024, nullptr); return msgBuf; } int main(int c, char **a) { WSADATA wsadata{}; // ┐ no i standardowo trzeba WSAStartup(MAKEWORD(2, 2),&wsadata); // ┘ zainicjalizować winsock const char *port = (c < 2 ? "4444" : a[1]); int family = (c < 3 ? AF_INET6 : AF_INET), status; // zwróć uwagę że tutaj jest ustawiane też socktype addrinfo *res, hints{.ai_flags = AI_PASSIVE, .ai_family = family, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP}; if ((status = getaddrinfo(nullptr, port, &hints, &res))) { std::cerr << "Getaddrinfo failed: " << gai_strerror(status) << std::endl; return 1; } SOCKET s = socket(res->ai_family, res->ai_socktype, 0), one = 1, zero = 0; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); // windows domyślnie dla AF_INET6 słucha tylko na IPv6; poniższa linia zmienia to zachowanie setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&zero, sizeof(zero)); if (bind(s, res->ai_addr, res->ai_addrlen) == SOCKET_ERROR) { std::cerr << "Bind failed: " << getLastErrorMessage() << std::endl; return 1; } freeaddrinfo(res); listen(s, 1); std::cout << "Listening on port " << port << std::endl; while (1) { sockaddr *cliAddr = (sockaddr *)new sockaddr_storage; socklen_t cliAddrLen = sizeof(sockaddr_storage); SOCKET c = accept(s, cliAddr, &cliAddrLen); if (c == INVALID_SOCKET) { perror("Bind failed"); return 1; } std::stringstream msg; msg << "Client connected from:" << std::endl; char host[NI_MAXHOST], port[NI_MAXSERV]; status = getnameinfo(cliAddr, cliAddrLen, host, NI_MAXHOST, port, NI_MAXSERV, 0); if (status) std::cerr << "Getnameinfo failed: " << gai_strerror(status) << std::endl; msg << "\r [resolved] address: " << host << " port: " << port << std::endl; status = getnameinfo(cliAddr, cliAddrLen, host, NI_MAXHOST, port, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV); if (status) std::cerr << "Getnameinfo failed: " << gai_strerror(status) << std::endl; msg << " [numeric ] address: " << host << " port: " << port << std::endl; delete cliAddr; std::cout << msg.str() << std::flush; send(c, msg.str().c_str(), msg.str().length(), 0); shutdown(c, SD_BOTH); closesocket(c); } } </code> ++++ ~~Zadanie.#~~ Przetestuj powyższy kod. Poproś osobę siedzącą obok żeby połączyła się do ciebie używając zarówno twojego adresu IPv4 i IPv6. \\ <small> Żeby dodatkowo łączyć się z wybranego numeru portu, możesz użyć opcji ''bind'' programu socat tak jak w tych przykładach: \\ ''socat tcp4:127.0.0.1:4444,bind=:3074 stdio'' ''socat tcp6:[::1]:4444,bind=:5222 stdio''. </small>
bio-psiec/ghbn_gai_gni.txt
· ostatnio zmienione: 2025/11/20 15:14 przez
jkonczak
Narzędzia strony
Pokaż stronę
Poprzednie wersje
Odnośniki
Złóż / rozłóż wszystko
Do góry