# SO Lista 9 ## zadanie 1 ![](https://i.imgur.com/qofSTzL.png) <!-- Gdyniarskie notki--> ### Protokoły warstwy łącza Warstwa sieciowa wysyła datagramy które wędrują przez wiele routerów będących pomiędzy źródłem a celem. Aby wysyłać pakiety z jednego wierzchołka (hosta lub routera) do drugiego, warstwa sieciowa polega na warstwie łącza i jej protokołach. Warstwa sieciowa w każdym napotkanym wierzchołku przekazuje datagram warstwie łącza, która przekazuje go do innego wierzchołka, z którego znów protokoły warstwy łącza przekazują go do warstwy sieciowej. ### Protokoły warstwy sieciowa Są one odpowiedzialne za transportowanie pakietów warstwy sieciowej, zwanych datagramami, z jednego hosta do drugiego. Protokoły warstwy transportowej przekazują segment warstwy transportowej i adres docelowy do warstwy sieciowej, niemalże jak nadawanie listu na pocztę z czyimś adresem. Warstwa sieciowa, udostępnia wtedy usługę dostarczenia segmentu do warswy transportowej hosta docelowego. Jednym z protokołów warstwy transportowej jest protokół IP, który definiuje pola w datagramie, tak jak zachowania ich względem. W tej warstwie są też protokoły, które ustalają ścieżki datagramów od źródła do końcowego hosta. ### Protokoły warstwy transportowa Ich rolą jest transportowanie wiadomości wyższej warstwy - application-layer, pomiędzy endpointami. Wyróżniamy dwa takie protokoły - UDP i TCP. ### Ramka jednostka komunikacji w warstwie łącza. Warstwa łącza enkasułuje pakiety z warstwy sieciowej i enkapsułuje je w ramki. Jeśli rozmiar ramki jest zbyt duży, wtedy pakiet może być rozdzielony na mniejsze ramki. ### Datagram pakiety warstwy sieciowej ### Segment pojedynczy fragment danych o pojemności 1024 bajtów To ogólnie idea jest taka że będziemy mieli wiele warstw, które pozwolą nam opakowywać nasze dane. ![](https://i.imgur.com/NBwqeZf.png) To tutaj będą te warstwy: ![](https://i.imgur.com/S5BWGnq.png) ![](https://i.imgur.com/pm7Xbw1.png) Pytają nas o łącza (Link Layer), sieciowej (Network Layer), transportowej (Transport Layer). Po krótce każde z nich: - Transport Layer - głównie jak rozumiem tylko dla TCP, tutaj są dane o Flow control oraz rozbijaniu wiadomości na krótsze segmenty - Network Layer - Przesyłanie tych segmentów Transport Layer między hostami (nazywamy to datagramami) - Link Layer - przesyłanie datagramów między punktami w naszej ścieżce (tworzy frame'y) Przechwycenie pakietów to jakieś odpal program kliknij "any" i sobie popatrz ### do czego służy kapsułkowanie To służy do takiego opakowywania danych w kolejne warstwy (tak jak ubieranie się na cebulke) i to ma pomóc z wysyłaniem danych ### pokaż frame'y itp ![](https://i.imgur.com/PfTwara.png) to tutaj np wziąłem pakiet TCP i od dołu mamy transport layer, network layer, link layer i no nazewnictwo też mamy za soba to wiadomo co jest segmentem, datagramem i ramką ### Zidentyfikuj adres źródłowy i docelowy pakietu ![](https://i.imgur.com/MpqcXnb.png) to po prostu mamy ładną tabelke i jest SOURCE I DESTINATION ### Czemu protokoły warstwy łącza i sieciowej nie są używane do komunikacji między procesami użytkownika? (chyba o to chodzi idk) W skrócie każde połączenie między procesem musi mieć socket (dużo pamięci) i każdy message musi przejść przez kernela jakimś syscallem więc lepiej już pójść w shared-memory i mieć to z głowy elo 320 ![](https://i.imgur.com/HedgYpa.png) ## zadanie 2 ![](https://i.imgur.com/fx30ahP.png) UDP (user datagram protocol) - protokół bezpołączeniowy, serwer i klient nie tworzą połączenia przed rozpoczęciem transmisji danych - przy użyciu jednego gniazda, możemy komunikować się z wieloma hostami - używamy pary funkcji sendto() i recvfrom(), w których określamy adres hosta do którego wysyłamy lub od którego odbieramy dane - brak gwaracji co do kompletności przesłanych danych TCP (transmission control protocol) - protokół połączeniowy, serwer i klient tworzą pomiedzy sobą połączenie, gniazdo ma przypisaną informację o parze hostów, które będą się komunikować - odczyty zapisy, wykonywane przy użyciu read, write - odbiór każdego segmentu jest potwierdzany, jeśli brak potwierdzenia - wyślij jeszcze raz **Komunikacja duplexowa** (full-duplex)- w każdej chwili możliwe jest przesyłanie danych w obie strony, **pól duplexowa** (half-duplex) - w danej chwili komunikacja może odbywać się tylko w jedną stronę (wysyłamy/odbieramy naprzemiennie) - TCP dzieląc przesyłane dane na segmenty nadaje im numery, na ich podstawie można odtworzyć ich prawidłową kolejność. Każdy segment musi zostać potwierdzony, brak potwierdzenie skutkuje retransmisją. - Zerwane połączenie - gniazdo TCP przechowuje informacje o czasie od ostatniej wymiany danych, a także liczbie retransmisji segmentu, gdy nie otrzymamy ACK. Po przekroczeniu granicznego czasu od ostatniego "kontaktu" lub granicznej liczby retransmisji, jądro zamknie gniazdo. Używanie funkcji systemowych na zamkniętym gnieździe zakończy się błędem, np `EPIPE` dla `read()`. - Sterowanie przepływem - protokół TCP dynamicznie informuje jaką liczbę bajtów może jeszcze przyjąć, zapobiega to przepełnieniu bufora. Kontrola nad liczbą równocześnie nadawanych (oczekujących na potwierdzenie) segmentów pozwala na zredukowanie obciążenia sieci (nie nadajemy niepotrzebnie) i zwiększenie przepustowości. :::spoiler W skrócie UDP - to jest właśnie wysłanie listu, piszemy wiadomość do swojej lubej ale poczta polska może nas uratować od k*biety i nigdy tego nie dostarczyć (i nie dowiemy się o tym) TCP - to jest jak dzwonimy do Saszki czy idziemy na piwko i od razu słychać "Kurwa stary jeszcze pytasz?" (mamy informacje zwrotną czy wiadomość doszła) ::: ![](https://i.imgur.com/zFAjzfo.png) Ogólnie TCP jest dopracowany, dane są numerowane w taki sposób, że nawet jak podczas przesyłania kolejność się zaburzy to program odczyta w poprawnej. Jak nie uda się połączyć z serwerem to próbujemy co jakiś czas (przez 4-10 minut). Jeszcze TCP wprowadza idee flow-control, czyli ile bajtów możemy przesłać/odczytać, by nie zapełnić np serwera. Komunikacja dupleksowa poniżej ale w skrócie: - Full Duplex, gdy możemy w dwie strony współbieżnie (gadanie przez telefon) - Half Duplex, nadal możemy w dwie strony ale tylko jedna strona na raz (np Walkie-Talkie i mówienie "odbiór") ![](https://i.imgur.com/rwk7GvN.png) Jak sobie radzi z zagubieniem segmentu i inną kolejnością co do kolejności było odpowiedziane a co do zagubionego segmentu wysyła kolejne zarządanie o ten segment (a jak ma duplikat to wie, że to duplikat i go wywala) ![](https://i.imgur.com/R6uEbwk.png) Skąd protokół TCP wie kiedy połączenie zostało zerwane? Wysyła po prostu retransmissions ![](https://i.imgur.com/1Ocod7Q.png) Jaki problem rozwiązuje sterowanie przepływem (ang. flow control) implementowane przez TCP? Że nie będzie sytuacji że stracimy pakiety i serwer będzie przeciążony ![](https://i.imgur.com/XGwA6cJ.png) ## zadanie 3 ![](https://i.imgur.com/HgbTqAv.png) ![](https://i.imgur.com/Ulh9ieH.png) ### Adres lokalny i zdalny ![](https://i.imgur.com/lMlEH2l.png) związanie następuje w connect() ### Porty ulotne w funkcji connect ![](https://i.imgur.com/MuxHfDX.png) ![](https://i.imgur.com/j25OOWd.png) ### Co specyfikuje drugi argument wywołania systemowego listen(2)? ![](https://i.imgur.com/c2QaX1Q.png) Jest to hint dla jądra ile requestów można zakolejkować zanim zaczniemy je z buta odrzucać ### Z jakim numerem portu jest związane gniazdo przekazywane do i zwracane z accept(2)? ![](https://i.imgur.com/Uq3IROR.png) Czyli przed wywołaniem mamy bind() i socket ma jakiś port który jest z nim związany ![](https://i.imgur.com/WeqtXm0.png) Tutaj nie jesteśmy pewni ale chyba po wywołaniu będzie ten sam port (powinien być) ### Skąd serwer wie, że klient zakończył połączenie? Dostaje EOF na twarz Read i wszystko się kończy ## zadanie 4 ![](https://i.imgur.com/T6YMuqY.png) ![](https://i.imgur.com/ypTYgGZ.png) To krótkie opisanie tego jest takie, że jak wcześniej musieliśmy zbudować most by się komunikować (połączenie serwer-klient), teraz możemy to olać i wysyłać dane prosto do serwera tylko z taką karteczką od kogo to jest ### Czemu, w przeciwieństwie do TCP, serwer może rozpocząć pracę zaraz po wykonaniu funkcji bind(2)? Bo jak mówiłem wysyłając dany bedziemy mówić od kogo to jest więc żadne "mosty" nie są nam potrzebne ### Z jakiej przyczyny interfejs read(2) i write(2) po stronie serwera może być niewystarczający? Bo nie mamy jednego połączenia a wiele, i jakoś trzeba rozróżnić od kogo odczytujemy (by wiedzieć do kogo wysłać) ### Przedstaw semantykę operacji recvfrom(2) i sendto(2). ![](https://i.imgur.com/KSgtlAq.png) Nie wiem czy coś mówić tutaj ### Kiedy po stronie klienta następuje związanie gniazda UDP z adresem lokalnym? Tutaj gościu ze stacka o to pytał i chodzi o to, że jak użyjemy sendto najpierw do naszego serwera to kernel nam zbinduje socket z adresem lokalnym ![](https://i.imgur.com/IqFaXIF.png) ### Na podstawie [ 7, 8.11] zreferuj efekt jaki przynosi wykonanie connect(2) na gnieździe klienta ![](https://i.imgur.com/8KbMaN9.png) ![](https://i.imgur.com/jiihByn.png) ![](https://i.imgur.com/LH6hu57.png) W skrócie chodzi nie możemy już zmieniać tego gdzie wysyłamy dane tylko do jednego serwera (i możemy używać write i read !!!!) ### Zalety msg ![](https://i.imgur.com/Q2l55On.png) ## zadanie 5 ![](https://i.imgur.com/P16MwQl.png) ### short count short count jest gdy socket jest zamykany gdy bufor wejścia wyjścia nie jest zapełniony do końca. ### skąd wiemy, że nie został obcięty datagram pytanie pułapka, nie wiemy czy został obcięty XDD (nie no tak mi się zdaje). W TCP są jakieś sumy kontrolne, nadawanie kolejności datagramom by jak coś się utraci to wysłać żądanie o ponowne wysłanie a w UDP nic takiego nie widziałem. UDP ma length i check sum ### jakie mogę dostać EINTR wszelkie z komunikacją, czyli read, write, reciv... i accept, który acceptuje połączenie. EINTR służy temu aby zawiadomić użytkownika, że operacja została interrupted sygnałem. ### Co gdy piszemy do zamkniętego socketa ![](https://i.imgur.com/aRpGsWV.png) niekoniecznie dostaniemy sigpipe, gdyż jeżeli użyliśmy funkcji connect() jest on handlowany przez protocol stack. Wówczas otrzymuje on EPIPE. ### dlaczego użyto SO_REUSEADDR i po co on jest ![](https://i.imgur.com/XCkGLbz.png) ![](https://i.imgur.com/veGxVUM.png) I on to robi jakoś tak że dodaje pośredni "telefon" w naszym połączeniu i jakbyśmy tego nie używali to dostaniemy EADDRINUSE na twarz i możemy iść płakać ## zadanie 6 ![](https://i.imgur.com/Ud8Iycr.png) ```clike= #include "csapp.h" int main(int argc, char **argv) { struct addrinfo *pv4, *pv6, *listpv4, *listpv6, hintsIPv4, hintsIPv6; char buf[MAXLINE]; int rc, flags; if (argc < 2) app_error("usage: %s <domain name>\n", argv[0]); /* Get a list of addrinfo records */ /* dodane by zobaczyć odpowiedni port dla protokołu */ struct servent *serviceServent = NULL; if(argc >= 3) { serviceServent = getservbyname(argv[2],NULL); } memset(&hintsIPv4, 0, sizeof(struct addrinfo)); /* nowe structy dla IPv6 */ memset(&hintsIPv6, 0, sizeof(struct addrinfo)); hintsIPv4.ai_family = AF_INET; /* IPv4 only */ hintsIPv4.ai_socktype = SOCK_STREAM; hintsIPv6.ai_family = AF_INET6; /* IPv6 only */ hintsIPv6.ai_socktype = SOCK_STREAM; /* Connections only */ if ((rc = getaddrinfo(argv[1], NULL, &hintsIPv4, &listpv4)) != 0) gai_error(rc, "getaddrinfo"); if ((rc = getaddrinfo(argv[1], NULL, &hintsIPv6, &listpv6)) != 0) gai_error(rc, "getaddrinfo"); /* Walk the list and display each IP address */ flags = NI_NUMERICHOST; /* Display address string instead of domain name */ pv6 = listpv6; for (pv4 = listpv4; pv4; pv4 = pv4->ai_next) { Getnameinfo(pv4->ai_addr, pv4->ai_addrlen, buf, MAXLINE, NULL, 0, flags); if(serviceServent == NULL) printf("%s\n", buf); else printf("%s:%d\n", buf, ntohs(serviceServent->s_port)); Getnameinfo(pv6->ai_addr, pv6->ai_addrlen, buf, MAXLINE, NULL, 0, flags); if(serviceServent == NULL) printf("%s\n", buf); else printf("[%s]:%d\n", buf, ntohs(serviceServent->s_port)); pv6 = pv6->ai_next; } /* Clean up */ freeaddrinfo(listpv4); freeaddrinfo(listpv6); return EXIT_SUCCESS; } ``` ## zadanie 7 ![](https://i.imgur.com/2TMmiVX.png) Modyfikacja jest prosta ```clike= static void sigint_handler(int sig) { /* TODO: Change control flow so that it does not return to main loop. */ siglongjmp(buf,1); } /* TODO: Print bytes received after SIGINT has been received. */ if(sigsetjmp(buf,1)) { printf("Server received %ld bytes\n",nread); exit(0); } ``` to ten cały watch netstat -ptn jest jakiś fikuśny ![](https://i.imgur.com/Q5wrfYu.png) Tutaj widać jakąś kolumnę z Local Address i Foreign Adress (to jest nasz adres i ten z którym się połączyliśmy) i na prawo mamy pid i nazwę programu to tak łatwo znaleźć który to serwer a który to klient Na tym ss'ie od razu są dwie instancje klienta. Drugiemu klientowi nie działa echo !!! bo serwer nasłuchuje cały czas pierwszego klienta (co widać, że ma foreign adress na adres tego 1 klienta) i dopóki tamten nie zamknie się nasz serwer będzie tylko słuchał jego. No i przy ctrl+c dostaniemy nowy state! CLOSE_WAIT ### wireshark To tutaj zazwyczaj dostajemy po 3 pakiety na jedno wysłanie Source Port (Client) -> Destination Port (Server) (wysłanie danych) Source Port (Port) -> Destination Port (Client) (odebranie danych) Source Port (Client) -> Destination Port (Server) (potwierdzenie że żyjemy) a jak kończymy działanie to to samo ale nie wysyłamy flagi (PUSH tylko FINISHED) i nie ma żadnych danych a jak serwer się zamknie to client po 2 wysłaniu dostanie flage RESET (czyli że wpisuje do zamkniętego fd) ![](https://i.imgur.com/VdR9mJH.png) ## zadanie 8 ![](https://i.imgur.com/7fB0nUq.png) jakoś działa ale i tak zrobiłem trochę po swojemu ```clike= #include "csapp.h" #include "rio.h" #define LISTENQ 10 #define MAXCLIENTS (PAGE_SIZE / sizeof(client_t)) typedef struct client { pid_t pid; /* Client process id */ size_t nread; /* Numer of bytes received so far */ } client_t; /* TODO: Need to define context to be used with sigsetjmp & siglongjmp. */ jmp_buf buf_jump; static client_t *client = NULL; static sig_atomic_t nclients = 0; static size_t nread = 0; /* number of bytest received on all connections */ static client_t *findclient(pid_t pid) { for (int i = 0; i < MAXCLIENTS; i++) if (client[i].pid == pid) return &client[i]; return NULL; } static client_t *addclient(void) { client_t *c = findclient(0); if (c) { c->pid = -1; /* XXX: must be filled in after fork */ c->nread = 0; nclients++; } return c; } static void delclient(pid_t pid) { client_t *c = findclient(pid); assert(c != NULL); nread += c->nread; c->pid = 0; nclients--; } static void sigchld_handler(int sig) { pid_t pid; /* TODO: Delete clients as they die. */ int n = nclients; for(int i = 0 ; i < n; i++) { pid = client[i].pid; int status; int fake_pid = Waitpid(pid,&status,WNOHANG); if(fake_pid == pid) { safe_printf("[%d] Disconnected!\n", pid); delclient(pid); } } } static void sigint_handler(int sig) { /* TODO: Change control flow so that it does not return to main loop. */ if(findclient(getpid()) == NULL) { safe_printf("Server received quit request!\n"); siglongjmp(buf_jump,1); } else { _exit(1); } } static void echo(client_t *c, int connfd) { size_t n; char buf[MAXLINE]; rio_t rio; rio_readinitb(&rio, connfd); while ((n = Rio_readlineb(&rio, buf, MAXLINE))) { Rio_writen(connfd, buf, n); c->nread += n; /* XXX: Uncomment line below and watch client behaviour. */ /* exit(0); */ } } int main(int argc, char **argv) { if (argc != 2) app_error("usage: %s <port>\n", argv[0]); sigset_t sig_mask; sigemptyset(&sig_mask); sigaddset(&sig_mask, SIGCHLD); //sigaddset(&sig_mask, SIGINT); Signal(SIGCHLD, sigchld_handler); Signal(SIGINT, sigint_handler); client = Mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); int listenfd = Open_listenfd(argv[1], LISTENQ); /* TODO: Wait for all clients to quit and print a message with nread. */ if(sigsetjmp(buf_jump,1)) { sigset_t mask; Sigprocmask(SIG_BLOCK, &sig_mask, &mask); while(nclients != 0) { Sigsuspend(&mask); } safe_printf("Server received %ld bytes\n",nread); exit(0); //TODO } while (1) { socklen_t clientlen = sizeof(struct sockaddr_storage); struct sockaddr_storage clientaddr; /* Enough space for any address */ int connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); static char client_hostname[MAXLINE], client_port[MAXLINE]; Getnameinfo((SA *)&clientaddr, clientlen, client_hostname, MAXLINE, client_port, MAXLINE, 0); sigset_t mask; Sigprocmask(SIG_BLOCK, &sig_mask, &mask); /* TODO: Start client in subprocess, close unused file descriptors. */ client_t *new_client = addclient(); int pid = Fork(); if(pid > 0) { new_client->pid = pid; Close(connfd); } else { safe_printf("[%d] Connected to %s:%s\n", getpid(), client_hostname, client_port); new_client->pid = getpid(); echo(new_client, connfd); Close(connfd); exit(1); } Sigprocmask(SIG_SETMASK, &mask, NULL); } } ```