===== 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''. (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:
#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''.