====== Programowanie połączeń sieciowych ====== ===== DO PRZYGOTOWANIA ===== * Proszę przypomnieć sobie użycie ''[[http://home.agh.edu.pl/~gjn/dydaktyka/UGLX/node11.html#SECTION000117000000000000000|fork()]]'' * Proszę poczytać opis podstaw gniazd sieciowych (socketów) z [[https://web.archive.org/web/20170405054449/http://www.linuxpl.org/LPG/node1.html|The Linux Programmer's Guide]]. Konkretnie sekcję [[https://web.archive.org/web/20170305021856/http://www.linuxpl.org:80/LPG/node81.html|Gniazda sieciowe - podstawy]] oraz jej podsekcje: "Podstawowe funkcje", "TCP: SOCK_STREAM", "SOCK_DGRAM (UDP)", "PF_UNIX", "SOCK_RAW i PF_PACKET" ===== WPROWADZENIE ===== ==== Network Byte Order ==== * Zapoznać się z następującymi pojęciami: * Network Byte Order * Big-Endian * Little-Endian * Zapoznać się z funkcjami: //htonl//, //htons//, //ntohl//, //ntohs// ==== getaddrinfo ==== * Przeczytać manual do funkcji //getaddrinfo// * Jakie parametry przyjmuje funkcja i jakie wartości zwraca. * Który parametr funkcji może jako wartość przyjąć //http// lub //ftp// lub //telnet// lub //smtp//? * Zwrócić uwagę na opis struktury: //struct addrinfo// * Co oznaczają poszczególne pola struktury i jakie wartości mogą przyjmować. * Jaki jest sens wprowadzenia pola //ai_next// w strukturze? ==== Obsługa adresów IPv4 oraz IPv6 ==== * Zapoznać się z następującymi strukturami (//man socket//, ///usr/include//): * //struct sockaddr// * //struct sockaddr_in// * //struct in_addr// * //struct sockaddr_in6// * //struct in6_addr// * Które z nich dotyczą protokołu IPv4 a które IPv6? * Jakie są zależności pomiędzy tymi strukturami? * Funkcje konwersji: * //inet_pton// - konwertuje zapis "192.168.1.1" na odpowiednią strukturę - czyli inaczej konwertuje ''string'' do reprezentacji binarnej. * //inet_ntop// - konwertuje strukturę (reprezentację binarną) na ''string''. ==== Gniazda ==== * Gniazdo z ang. //socket//. * Są używane w czwartej warstwie sieciowego modelu OSI/ISO. * Otwieranie gniazd (i uzyskanie deskryptora do komunikacji sieciowej) dokonuje się za pomocą funkcji socket(int domain, int type, int protocol) * Adres IP identyfikuje hosta w danej sieci (podsieci), co identyfikuje //numer portu//? * Czym różni się deskryptor gniazda od deskryptora pliku? * Istnieje kilka rodzajów socketów w tym: * //Stream Socket// - służą do komunikacji połączeniowej (użycie TCP) * //datagram Socket// - służą do komunikacji bezpołączeniowej (użycie UDP) * Powiązanie numeru portu z deskryptorem gniazda dokonuje się za pomocą funkcji bind(int sockfd, struct sockaddr *my_addr, int addrlen) * Proszę przeczytać manuale dla funkcji ''socket(2)'' i ''bind(2)'', zwrócić uwagę na parametry jakie przyjmują i wartości jakie zwracają. Podstawowe funkcje systemowe: {{ sockets-comm.png}} * ''socket(2)'' * ''bind(2)'' * ''listen(2)'' * ''accept(2)'' * ''connect(2)'' === Połączenie === * Do nawiązywania połączeń wykorzystujemy funkcję connect(int sockfd, struct sockaddr *serv_addr, int addrlen) * ''sockfd'' - deskryptor gniazda * ''serv_addr'' - adres hosta docelowego, który możemy otrzymać przy pomocy funckji ''getaddrinfo'' * ''addrlen'' - długość adresu, najczęściej podaje się wartość ''addrinfo::ai_addrlen'' * Dopiero po pomyślnym nawiązaniu połączenia możemy używać ''sockfd'' do komunikowania się z serwerem. === Nasłuchiwanie === * Rozpoczęcie nasłuchiwania nie wymaga użycia funkcji ''connect'' ponieważ to zdalny klient będzie jej używał do połączenia się z naszym serwerem. * Nasłuchiwanie można rozpocząć przy pomocy funkcji listen(int sockfd, int backlog) * ''sockfd'' - deskryptor gniazda. Nasłuchiwanie będzie się odbywać zgodnie z parametrami opisywanymi przez deskryptor. * ''backlog'' - maksymalna liczba połączeń oczekujących na akceptaję. * Ostatnim krokiem rozpoczęcia komunikacji z klientem jest akceptacja jego próby połączenia. Dokonuje się tego za pomocą funkcji: accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) * Funkcja ''accept(2)'' jako wartość zwraca nowy deskryptor gniazda który służy do komunikacji z akceptowanym połączeniem. === Wysyłanie/odbieranie danych === * Wszystko w systemach GNU/Linux/Unix jest reprezentowane za pomocą plików - tak więc gniazda również. * Wysyłanie/odbieranie danych przez/z gniazd jest bardzo podobne do zapisu/odczytu danych do/z pliku. * Jest tak podobne, że do tego celu można użyć funkcji ''write(2)'', ''read(2)'' :!: * Jednak system oferuje funkcje specjalizowane ''send(2)'', ''recv(2)'' które oferują dodatkową konfigurację. * Proszę przeczytać manual dla powyższych funkcji zwracając uwagę na: * przyjmowane parametry * zwracane wartości === Zamknięcie połączenia === * Po zakończeniu wysyłania/odbierania danych należy zamknąć połączenie. * Zamknięcie połączenia reprezentowanego przez dany deskryptor można dokonać przy pomocy funkcji ''close(2)'' * Dla zainteresowanych: porównać funkcję ''close(3)'' z funkcją ''shutdown(3)''. ===== ĆWICZENIA ===== ==== Sockety w Bashu ==== * Jest możliwe otworzenie Socketa w Bashu za pomocą następującej składni: exec {deskryptor-pliku}<>/dev/tcp/{host}/{port} * Np. aby otworzyć dwukierunkowego socketa dla strony Google z portem HTTP i deskryptorem nr 3 (dlaczego akurat taki?) należy napisać: exec 3<>/dev/tcp/www.google.pl/80 * Uruchom i przeanalizuj poniższe przykłady: #!/bin/bash ### # Połącz się ze stroną internetową i pobierz zawartość strony głównej ### exec 3<>/dev/tcp/www.google.pl/80 echo -e "GET / HTTP/1.1\nHost: www.google.pl\nConnection: close\n\n" >&3 cat <&3 #!/bin/bash ### # Pobierz aktualny czas z serwera NTP ### cat #!/bin/bash ### # Skaner portów (sprawdza które porty są otwarte). # Jako argument wywołania podaj adres serwera, który chcesz przeskanować, # np. ./port-scanner.sh localhost ### host=$1 port_first=1 port_last=65535 for ((port=$port_first; port<=$port_last; port++)) do # echo "Skanowanie portu $port..." timeout 1 bash -c "(echo >/dev/tcp/$host/$port) >/dev/null 2>&1" && echo "$port otwarty!" done ==== Programowanie gniazd ==== Przeglądnąć artykuł: [[http://beej.us/guide/bgnet/html/single/bgnet.html|Beej's Guide to Network Programming]] Skompilować i przetestować omówione w nim programy, w tym: {{:pl:dydaktyka:unix:client.c}} + {{:pl:dydaktyka:unix:server.c}} (uwaga na nr portu!) {{:pl:dydaktyka:unix:listener.c}} + {{:pl:dydaktyka:unix:talker.c}} (uwaga na nr portu!) Kompilacja: gcc -Wall -o server server.c ==== gethostbyname ==== - Proszę przeanalizować, skompilować i uruchomić program: #include #include #include /* 4 gethostbyname, hostent structure */ #include /* 4 exit */ #include /* 4 ntohn */ int main(int argc, char *argv[]) { int i,j; struct hostent *he; if (argc != 2) { fprintf(stderr,"usage: %s hostname\n", argv[0]); return 1; } if ((he = gethostbyname(argv[1])) == NULL) { fprintf(stderr, "gethostbyname error\n"); return 1; } /* host info: */ printf("\nHost name: %s", he->h_name); printf("\nAliases:"); for(i=0;he->h_aliases[i] != NULL;++i) printf("\n%d. %s", i+1, he->h_aliases[i]); if(he->h_addrtype == AF_INET) printf("\nAddres type: IPv4"); if(he->h_addrtype == AF_INET6) printf("\nAddres type: IPv6"); printf("\nAddress length: %d bytes", he->h_length); printf("\nAddresses:"); for(j=0;jh_length;++j) { printf("%d", (uint8_t)he->h_addr[j]); if(j < (he->h_length-1)) printf("."); } printf("\n"); return 0; } - Sprawdzić działanie programu dla ''www.yahoo.com'' oraz innych wybranych adresów symbolicznych. - Dopisać instrukcje, które szczegółowo sprawdzają typ błędu funkcji ''gethostbyname'' i w zależności od tego wyświetlają odpowiedni komunikat. - Zmodyfikować tak program aby wyświetlał wszystkie adresy IP odnoszące się do podanego adresu. ==== Serwer ==== Poniżej przedstawiony jest kod programu //server.c// /* ** server.c -- a stream socket server demo */ #include #include #include #include #include #include #include #include #include #include #include #define MYPORT 3490 // the port users will be connecting to #define BACKLOG 10 // how many pending connections queue will hold void sigchld_handler(int s) { while(waitpid(-1, NULL, WNOHANG) > 0); } int main(void) { int sockfd, new_fd; // listen on sock_fd, new connection on new_fd struct sockaddr_in my_addr; // my address information struct sockaddr_in their_addr; // connector's address information socklen_t sin_size; struct sigaction sa; int yes=1; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } my_addr.sin_family = AF_INET; // host byte order my_addr.sin_port = htons(MYPORT); // short, network byte order my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero); if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr) == -1) { perror("bind"); exit(1); } if (listen(sockfd, BACKLOG) == -1) { perror("listen"); exit(1); } sa.sa_handler = sigchld_handler; // reap all dead processes sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) == -1) { perror("sigaction"); exit(1); } sin_size = sizeof their_addr; if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) { perror("accept"); } printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr)); if (send(new_fd, "Hello, world!\n", 14, 0) == -1) perror("send"); sleep(5); // just for observing easily that the server cannot serve a few clients concurrently close(new_fd); return 0; } === - Komunikator === * Należy przerobić powyższy program tak aby działał jako server. Łącząc się za pomocą np. programu //telnet// program powinien umożliwiać prowadzenie dialogu jak popularne komunikatory internetowe (np. gg, tlen, itp). * Korzystając z programu telnet można połączyć się z serwerem wpisując: $ telnet remotehostname XXX gdzie ''remotehostname'' jest nazwą komputera, na którym uruchomiono serwer (''localhost'', jeżeli to ta sama maszyna), a ''XXX'' numerem przypisanego do niego portu. * Podpowiedź: po akceptacji połączenia program powinien utworzyć dwa procesy potomne, jeden do czytania portu, drugi do pisania. === - Eternal vigilance == * Proszę zmodyfikować serwer tak, aby po obsłużeniu klienta nie kończył działania, ale powracał do oczekiwania na kolejne połączenie. === - Obsługa wielu klientów === * Proszę zmodyfikować serwer tak, aby mógł obsługiwać jednocześnie więcej niż jednego klienta. * Podpowiedź: Można użyć funkcji ''fork()'' do tworzenia procesów potomnych - każdy proces potomny będzie obsługiwał jednego klienta. ===== Dla poszerzenia wiedzy ===== * [[http://tools.ietf.org/|IETF]] * [[http://www.kohala.com/start/|W. Richard Stevens' Home Page]] * BSD sockets * http://www.frostbytes.com/~jimf/papers/sockets/sockets.html * http://www.lowtek.com/sockets/ * http://beej.us/guide/bgnet/ * http://www.uwo.ca/its/doc/courses/notes/socket/ * http://gaia.cs.umass.edu/ntu_socket/ * http://www.devdaily.com/Dir/Unix/Socket_Programming/ * http://www.unl.csi.cuny.edu/faqs/sock-faq/html/unix-socket-faq.html * http://www.ibm.com/developerworks/linux/library/l-sockpit/ * OpenSSL * http://www.ibm.com/developerworks/linux/library/l-openssl.html * http://www.ibm.com/developerworks/linux/library/l-openssl2.html * http://www.ibm.com/developerworks/linux/library/l-openssl3.html * http://www.ibm.com/developerworks/linux/library/l-hisock.html * TCP/IP * http://userpages.umbc.edu/~jeehye/cmsc491b/lectures/tcpstate/sld001.htm * http://www.tcpipguide.com/free/ * http://www.ipprimer.com/overview.cfm * http://www.linux-tutorial.info/modules.php?name=MContent&obj=page&pageid=142 * Użycie Select * [[http://www.lowtek.com/sockets/select.html|The World of select()]]