===== 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 ''netcat'' z przełącznikami ''-ptW'' oraz ''-ptW -n'', lub programie ''ss'' z przełącznikami ''-pt -r'' oraz ''-pt -n''. (W programie ''ss'' możesz dodać też do argumentów ''dst antares.put.poznan.pl'' żeby zobaczyć tylko połączenia kierowane pod ten adres.) ~~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:
#include #include #include 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; } ~~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.
#include #include #include #include #include #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; }
Wyjaśnij skąd biorą się wypisane na standardowe wyjście wyniki. 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''). 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'':
#include #include #include #include // 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 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; }
++++ Kod dla MS Windows |
#include #include #include #include // ┐ 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 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; }
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:
Protokół warstwy sieci:
Rodzaj gniazda:
Protokół warstwy transportowej:
Opcje:
                
===== 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:
#include #include #include #include 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); } }
++++ Kod dla MS Windows |
#include #include #include // ┐ #include // ├ 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); } } ++++ ~~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. \\ Ż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''.