wtorek, 14 lutego 2017

TCP/IP

Komunikacja w sieci oparta jest na warstwowych modelach protokołów i odniesienia. Najpopularniejsze modele warstwowe to:
- model odniesienia ISO/OSI,
- model protokołów TCP/IP.

Model ISO/OSI traktowany jest jako model odniesienia (wzorzec) dla większości rodzin protokołów komunikacyjnych. Podstawowym założeniem modelu jest podział systemów sieciowych na 7 warstw (ang. layers) współpracujących ze sobą w ściśle określony sposób.

Dla internetu sformułowano uproszczony model TCP/IP, który ma tylko 4 warstwy.



W modelu TCP/IP każda wiadomość wysłana przez aplikację przechodzi przez wszystkie warstwy TCP/IP, od najwyższej warstwy aplikacji do najniższej warstwy dostępu do sieci (każda warstwa dodaje swój własny nagłówek do danych aplikacji - zjawisko to nazywamy enkapsulacją). Następnie wiadomość jest transmitowana przez sieć do drugiego komputera. Na koniec przechodzi przez wszystkie warstwy w przeciwnym kierunku, aż do warstwy aplikacji i docelowego procesu. Usuwanie tych nagłówków po drugiej stronie komunikacji nazywamy dekapsulacją.

Na rysunku poniżej pokazano przykładową sesję TCP, która odbyła się w sieci lokalnej pomiędzy klientem 192.168.0.17 a serwerem 192.168.0.23. Klient 192.168.0.17 po połączeniu się do serwera 192.168.0.23 na porcie 6996 (za pomocą narzędzia Telnet) otrzymuje od niego komunikat "Hello". Następnie klient jako pierwszy zamyka połączenie (wychodzi z programu Telnet).
Kod źródłowy i binarny serwera:
https://github.com/adamblaszczyk/tcp-test-server




Mamy tu 7 ramek a właściwie 7 segmentów TCP. Pierwsze trzy to ustanowienie połączenia, czyli uzgadnianie trój-etapowe (three-way handshake):

1) Klient 192.168.0.17 wysyła segment SYN z losowym numerem sekwencyjnym Seq=1796127574
2) Serwer 192.168.0.23 wysyła segment SYN ACK ze swoim losowym numerem sekwencyjnym Seq=1444501807 oraz numerem potwierdzenia Ack=1796127575 (numer sekwencyjny klienta zwiększony o 1)
3) Klient wysyła segment ACK ze swoim numerem sekwencyjnym Seq=1796127575 oraz numerem potwierdzenia Ack=1444501808 (numer sekwencyjny serwera zwiększony o 1)

To kończy proces nawiązywania połączenia i pozwala na właściwą transmisję danych:
4) Wysłanie właściwych danych przez serwer - tekst "Hello" (5 bajtów) - ustawiona flaga ACK (Seq=1444501808; Ack=1796127575)
5)  Klient wysyła segment ACK potwierdzający odebranie danych (Seq=1796127575; Ack=1444501813 - numer sekwencyjny serwera zwiększony o ilość odebranych bajtów)

Zakończenie połączenia:
6) Klient wysyła segment FIN ACK kończący połączenie (Seq=1796127575; Ack=1444501813)
7) Serwer potwierdza segmentem ACK (Seq=1444501813; Ack=1796127576)
Następuje zamknięcie sesji TCP.

Nie jest to do końca wzorcowe zakończenie połączenia TCP. Może ono być zainicjowane przez dowolną stronę i powinno ono przebiegać tak:
1. Host A wysyła segment FIN ACK do hosta B
2. Host B potwierdza wysyłając segment ACK do hosta B
3. Host B wysyła segment FIN ACK do hosta A.
4. Host A potwierdza wysyłając segmnet ACK do hosta B.
Dopuszcza się również awaryjne przerwanie połączenia poprzez przesłanie pakietu z flagą RST. Pakiet taki nie wymaga potwierdzenia.

Całą naszą sesję TCP można zobaczyć na poniższym rysunku:



Możemy teraz zobrazować proces enkapsulacji na przykładzie ramki czwartej - tam gdzie serwer wysyła dane. Wszystkie dane w ramce zaprezentowane są w postaci liczb szesnastkowych.

Na początku mamy dane warstwy aplikacji:
48 65 6c 6c 6f - tekst "Hello"

Następnie dodawany jest nagłówek TCP warstwy transportowej:
1b 54 9d f5 56 19 59 30 6b 0e bb 57 50 18 ff ff 7b 83 00 00
1b 54 - port nadawcy = 6996
9d f5 - port odbiorcy = 40437
56 19 59 30 - numer sekwencyjny Seq = ‭1444501808
6b 0e bb 57 - numer potwierdzenia Ack = ‭1796127575‬
5 - długość nagłówka 5x4 bajty = 20 bajtów
0 18 - flagi (PSH, ACK) 000 000011000 
ff ff - szerokość okna = 65535
7b 83 - suma kontrolna
00 00 - wskaźnik priorytetu
Powstaje segment TCP.

Potem dodawany jest nagłówek IPv4 warstwy internetowej:
45 00 00 2d 02 d1 40 00 80 06 76 81 c0 a8 00 17 c0 a8 00 11
4 - wersja protokołu IP = IPv4
5 - długość nagłówka 5x4 bajty = 20 bajtów
00 - Differentiated Services Field
00 2d - długość całkowita pakietu = 45
02 d1 - identyfikator
40 00 - flagi (don't fragment) + przemieszczenie fragmentacji
80 - Time To Live = 128
06 - protokół = 6 = TCP
76 81 - suma kontrolna nagłówka
c0 a8 00 17 - źródłowy adres IP = 192.168.0.23
c0 a8 00 11 - docelowy adres IP = 192.168.0.17

Powstaje pakiet IP.

Następnie dodawany jest nagłówek Ethernet warstwy dostępu do sieci:
00 21 63 23 55 d1 00 21 00 ae 81 e6 08 00
00 21 63 23 55 d1 - docelowy adres MAC
00 21 00 ae 81 e6 - źródłowy adres MAC
08 00 - typ = IPv4

Powstaje ramka ethernetowa.

Dodatkowe pojęcia

Numer portu warstwy transportowej - odpowiada za dostarczenie danych do właściwej aplikacji. Każda aplikacja w systemie operacyjnym posiada swój identyfikator jednoznacznie ją określający. Identyfikatorem tym jest numer portu aplikacji. Serwer WWW domyślnie pracuje na porcie 80/TCP, serwer pocztowy POP3 na porcie 110/TCP a serwer DNS na porcie 53/UDP.

Nie można uruchomić dwóch różnych serwerów (procesów) nasłuchujących na tym samym porcie. Funkcja bind() zwróci wtedy błąd 10048:
Windows Sockets Error Codes

Gniazdo sieciowe (ang. socket) to kombinacja adresu IP, numeru portu i użytego protokołu warstwy transportowej (np. TCP, UDP). Unikalna kombinacja tych trzech parametrów pozwala na zidentyfikowanie właściwego procesu, do którego wiadomość powinna być dostarczona.

Proces powiązywania aplikacji i gniazda jest nazywany przypisaniem (ang. binding).

Omawiany na powyższym przykładzie ruch w sieci został przechwycony za pomocą sniffera Wireshark , który jest darmowym i wolnym oprogramowaniem dla systemów z rodziny Linux, macOS i Windows.