|
|
pl:dydaktyka:unix:lab_prog_siec [2018/01/15 18:08] kkutt [Programowanie gniazd] |
pl:dydaktyka:unix:lab_prog_siec [2019/06/27 15:50] |
====== 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 <code c>socket(int domain, int type, int protocol)</code> | |
* 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 <code c>bind(int sockfd, struct sockaddr *my_addr, int addrlen)</code> | |
* 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ę <code c>connect(int sockfd, struct sockaddr *serv_addr, int addrlen)</code> | |
* ''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 <code c>listen(int sockfd, int backlog)</code> | |
* ''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: <code c>accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)</code> | |
* 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: <code>exec {deskryptor-pliku}<>/dev/tcp/{host}/{port}</code> | |
* Np. aby otworzyć dwukierunkowego socketa dla strony Google z portem HTTP i deskryptorem nr 3 (dlaczego akurat taki?) należy napisać: <code>exec 3<>/dev/tcp/www.google.pl/80</code> | |
| |
* Uruchom i przeanalizuj poniższe przykłady: | |
<file bash webpage.sh> | |
#!/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 | |
</file> | |
| |
<file bash timeserver.sh> | |
#!/bin/bash | |
| |
### | |
# Pobierz aktualny czas z serwera NTP | |
### | |
| |
cat </dev/tcp/time.nist.gov/13 | |
</file> | |
| |
<file bash port-scanner.sh> | |
#!/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 | |
</file> | |
| |
==== 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:<file c gethostbyname-demo.c> | |
#include <stdio.h> | |
#include <errno.h> | |
#include <netdb.h> /* 4 gethostbyname, hostent structure */ | |
#include <unistd.h> /* 4 exit */ | |
#include <netinet/in.h> /* 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;j<he->h_length;++j) | |
{ | |
printf("%d", (uint8_t)he->h_addr[j]); | |
if(j < (he->h_length-1)) | |
printf("."); | |
} | |
printf("\n"); | |
| |
return 0; | |
} | |
</file> | |
- 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// | |
<file c server.c> | |
/* | |
** server.c -- a stream socket server demo | |
*/ | |
| |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <errno.h> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <arpa/inet.h> | |
#include <sys/wait.h> | |
#include <signal.h> | |
| |
#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; | |
} | |
</file> | |
| |
=== - 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: <code sh>$ telnet remotehostname XXX</code> 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()]] | |
| |