Połączenie TCP
Protokół TCP jest nazywany zorientowany na połączenie, ponieważ zanim jeden proces aplikacji może zacząć wysyłać dane do drugiego, oba procesy muszą przeprowadzić między sobą proces negocjacji. Polega on na wzajemnym przesłaniu wstępnych segmentów wymaganych do ustalenia parametrów związanych z transferem danych, który ma nastąpić. W ramach procesu nawiązywania połączenia TCP obie jego strony zainicjują wiele zmiennych stanu powiązanych z połączeniem.
Połączenie TCP nie jest międzywęzłowym obwodem TDM lub FDM stosowanym w przypadku sieci z przełączaniem obwodów. Zamiast tego występuje tu połączenie logiczne ze wspólnym stanem przechowywanym tylko jako stan TCP w dwóch komunikujących się systemach końcowych. Ze względu na to, że protokół TCP funkcjonuje tylko w systemach końcowych, pośredniczące elementy sieciowe (routery i przełączniki warstwy łącza) nie utrzymują stanu połączenia TCP. Tak naprawdę pośredniczące routery są całkowicie nieświadome istnienia takich połączeń. Routery rozpoznają datagramy, a nie połączenia.
VINTON CERF, ROBERT KAHN I PROTOKÓŁ TCP/IP Na początku lat 70. sieci z przełączaniem pakietów zaczęły się rozpowszechniać. Sieć ARPAnet, będąca prekursorem internetu, była tylko jedną z wielu sieci. Każda z sieci posiadała własny protokół. Dwaj naukowcy, Vinton Cerf i Robert Kahn, uświadomili sobie, jak ważne jest połączenie tych sieci ze sobą. W związku z tym stworzyli uniwersalny protokół TCP/IP (ang. Transmission Control Protocol/Internet Protocol). Choć Cerf i Kahn najpierw postrzegali protokół jako całość, później podzielono go na dwa protokoły — TCP i IP, które funkcjonowały niezależnie. W maju 1974 r. Cerf i Kahn w dokumencie IEEE Transactions on Communications Technology [Cerf 1974] zawarli wyniki swoich prac dotyczących protokołu TCP/IP.
Protokół TCP/IP, będący obecnie fundamentem internetu, został opracowany przed pojawieniem się komputerów PC, stacji roboczych, smartfonów, przed rozpowszechnieniem się Ethernetu, sieci kablowych, sieci DSL, sieci Wi-Fi i innych technologii dostępowych, a także zanim zaczęto korzystać z technologii WWW, mediów społecznościowych i strumieniowej transmisji wideo. Cerf i Kahn dostrzegli potrzebę zdefiniowania protokołu sieciowego, który z jednej strony zapewni rozbudowaną obsługę jeszcze nieistniejących aplikacji, a z drugiej umożliwi współpracę dowolnym hostom i protokołom warstwy łącza danych.
W 2004 r. Cerf i Kahn otrzymali Nagrodę Turinga organizacji ACM (uznawaną za „Nagrodę Nobla w dziedzinie informatyki”) za „pionierską pracę nad łączeniem sieci, w tym nad projektowaniem i implementowaniem podstawowych internetowych protokołów komunikacyjnych (TCP/IP) oraz inspirujące przywództwo w obszarze sieci”.
|
Połączenie TCP zapewnia usługę pełnego dupleksu. Jeśli między procesem A uaktywnionym na jednym hoście i procesem B uruchomionym na drugim hoście istnieje połączenie TCP, w tym samym czasie dane warstwy aplikacji mogą być przesyłane od procesu A do procesu B i odwrotnie. Połączenie TCP jest też zawsze połączeniem punkt-punkt, czyli między jednym nadawcą i jednym odbiorcą. Tak zwane rozsyłanie grupowe (zob. dodatkowe materiały dostępne w internecie), czyli transfer danych w ramach pojedynczej operacji od jednego nadawcy do wielu odbiorców, nie jest możliwe do zrealizowania przy użyciu protokołu TCP. W przypadku tego protokołu dwa hosty to towarzystwo, a trzy to już tłum!
Przyjrzyjmy się teraz temu, w jaki sposób jest nawiązywane połączenie TCP. Załóżmy, że proces uruchomiony na jednym hoście zamierza zainicjować połączenie z innym procesem uaktywnionym na drugim hoście. Wcześniej wspomniano, że proces nawiązujący połączenie jest nazywany procesem klienta, natomiast drugi proces określa się mianem procesu serwera. Proces aplikacji informuje najpierw warstwę transportową klienta o tym, że zamierza ustanowić połączenie z procesem serwera. W punkcie 2.7.2 stwierdzono, że program klienta napisany w języku Python w tym celu stosuje następującą instrukcję:
Zmienna serverName identyfikuje nazwę serwera, natomiast serverPort — proces serwera. Warstwa transportowa klienta rozpoczyna działania mające na celu nawiązanie połączenia TCP z serwerem. Na końcu niniejszego podrozdziału bardziej szczegółowo omówiono proces ustanawiania połączenia. W tym miejscu wystarczy wiedzieć, że najpierw klient wysyła specjalny segment TCP, a następnie w odpowiedzi serwer odsyła drugi specjalny segment TCP. Na końcu klient ponownie odpowiada, wysyłając trzeci specjalny segment. Pierwsze dwa segmenty nie zawierają żadnych danych warstwy aplikacji, natomiast w trzecim segmencie mogą się znajdować takie dane. Ponieważ trzy segmenty są przesyłane między dwoma hostami, procedura nawiązywania połączenia jest często nazywana negocjowaniem 3-etapowym.
Po ustanowieniu połączenia TCP dwa procesy aplikacji mogą wysyłać do siebie dane. Pod uwagę weźmy przesłanie danych między procesem klienta i procesem serwera. Proces klienta przekazuje strumień danych za pośrednictwem gniazda (drzwi), co opisano w podrozdziale 2.7. Gdy dane przejdą przez gniazdo, zajmie się nimi protokół TCP klienta. Jak widać na rysunku 3.28, protokół TCP kieruje dane do bufora nadawczego połączenia, będącego jednym z buforów definiowanych podczas wstępnego 3-etapowego procesu negocjowania. Od czasu do czasu protokół TCP pobierze porcje danych znajdujących się w buforze nadawczym. Interesujące jest to, że specyfikacja protokołu TCP [RFC 793] bardzo ogólnie określa, kiedy właściwie protokół powinien wysłać buforowane dane. W specyfikacji napisano, że protokół TCP powinien przesłać dane w segmentach, gdy będzie mu to odpowiadało. Maksymalna ilość danych, które mogą zostać pobrane z bufora i umieszczone w segmencie, jest ograniczona przez maksymalny rozmiar segmentu (ang. Maximum Segment Size — MMS). Wartość parametru MSS jest zwykle ustalana przez określenie najpierw długości największej ramki warstwy łącza danych (parametr MTU — ang. Maximum Transmission Unit), która może być przesłana przez lokalny host nadawczy, a następnie zastosowanie wartości parametru MSS zapewniającej, że segment TCP (kapsułkowany w datagramie IP) razem z nagłówkiem TCP/IP (zwykle 40 bajtów) zmieści się w pojedynczej ramce warstwy łącza. W protokołach warstwy łącza danych Ethernet i PPP MTU wynosi 1500 bajtów. Dlatego typowa wartość parametru MSS to 1460 bajtów. Istnieją też propozycje rozwiązań określających wartość parametru Path MTU [RFC 1191] (rozmiar największej ramki warstwy łącza, którą można przesłać wszystkimi łączami znajdującymi się między węzłem źródłowym i docelowym), a także ustawiania wartości parametru MSS na podstawie wartości parametru Path MTU. Warto zauważyć, że parametr MSS określa maksymalną ilość danych warstwy aplikacji zawartych w segmencie, a nie maksymalny rozmiar segmentu TCP z uwzględnieniem nagłówka (choć jest to zagmatwane, musimy się z tym pogodzić, ponieważ od dawna funkcjonuje to już w ten sposób).
Każdą porcję danych klienta protokół TCP łączy z nagłówkiem, tworząc segmenty TCP. Segmenty są przekazywane warstwie sieci, która niezależnie kapsułkuje je w datagramach IP. Datagramy IP są następnie umieszczane w sieci. Po odebraniu segmentu przez protokół TCP drugiego węzła znajdujące się w nim dane zostaną wprowadzone do bufora odbiorczego połączenia TCP (rysunek 3.28). Aplikacja wczytuje strumień danych zlokalizowanych w buforze. Każda strona połączenia posiada własny bufor nadawczy i odbiorczy (na stronie http://www.awl.com/kurose-ross znajduje się animacja ilustrująca działanie buforów odbiorczego i nadawczego).
Rysunek 3.28. Bufory nadawcze i odbiorcze protokołu TCP
Z powyższego omówienia można wywnioskować, że połączenie TCP składa się z buforów, zmiennych i gniazda połączenia powiązanego z procesem uruchomionym na jednym hoście oraz podobnego zestawu buforów, zmiennych i gniazda skojarzonego z procesem innego hosta. Jak wcześniej wspomniano, urządzeniom sieciowym (routery, przełączniki i repeatery) pośredniczącym w połączeniu nawiązanym między hostami nie są przydzielane żadne bufory lub zmienne.
Struktura segmentu TCP
Po krótkim przedstawieniu połączenia TCP przyjrzyjmy się strukturze segmentu TCP.
Składa się on z pól nagłówka i pola danych. Pole danych zawiera porcję danych aplikacji. Jak wcześniej wspomniano, parametr MSS określa maksymalny rozmiar pola danych segmentu. Gdy protokół TCP wysyła duży plik, taki jak plik obrazu stanowiącego część strony internetowej, zwykle dzieli go na porcje o wielkości wyznaczonej przez parametr MSS (z wyjątkiem ostatniej porcji, która często będzie mniejsza). Jednak aplikacje interaktywne często transmitują porcje danych o rozmiarze mniejszym od określonego przez parametr MSS. Przykładowo, w przypadku aplikacji zdalnego logowania, takich jak narzędzie Telnet, wielkość pola danych segmentu TCP często wynosi tylko jeden bajt. Ponieważ rozmiar nagłówka segmentu TCP zazwyczaj jest równy 20 bajtom (12 bajtów więcej niż w przypadku nagłówka segmentu UDP), segmenty wysyłane przez program Telnet mogą liczyć 21 bajtów.
Na rysunku 3.29 zaprezentowano strukturę segmentu TCP. Jak w przypadku segmentu UDP, nagłówek segmentu TCP uwzględnia numer portu źródłowego i docelowego, które są wykorzystywane na potrzeby multipleksowania i demultipleksowania danych do lub z wyżej położonej warstwy aplikacji. Podobnie jak segment UDP nagłówek segmentu TCP zawiera pole sumy kontrolnej. W skład nagłówka segmentu TCP wchodzą też następujące pola:
Rysunek 3.29. Struktura segmentu TCP
- 32-bitowe pole numeru sekwencyjnego i 32-bitowe pole numeru potwierdzenia. Pola są używane przez stronę nadawczą i odbiorczą protokołu TCP w celu zapewnienia usługi niezawodnego transferu danych (więcej o tym poniżej).
- 16-bitowe pole okna odbiorczego używanego na potrzeby kontroli przepływu. Wkrótce okaże się, że pole to określa liczbę bajtów, które przyjmie odbiorca.
- 4-bitowe pole długości nagłówka określa długość nagłówka segmentu TCP wyrażoną za pomocą 32-bitowych słów. Ze względu na pole opcji nagłówek może mieć zmienną długość (zwykle pole opcji jest puste, dlatego zazwyczaj nagłówek segmentu TCP ma długość wynoszącą 20 bajtów).
- Opcjonalne pole opcji o zmiennej długości jest stosowane, gdy nadawca i odbiorca negocjują maksymalny rozmiar segmentu (parametr MSS). Wartość tego pola jest też używana w roli współczynnika skalowania okna w przypadku sieci o dużej szybkości. W polu można też zdefiniować znacznik czasu. Dodatkowe szczegóły można znaleźć w dokumentach RFC 854 i RFC 1323.
- Pole znaczników zawiera 6 bitów. Bit ACK określa, czy poprawna jest wartość umieszczona w polu potwierdzenia. Wartość wskazuje, że segment posiada potwierdzenie powiązane z prawidłowo odebranym segmentem. Bity RST, SYN i FIN są stosowane na potrzeby nawiązywania i przerywania połączenia (więcej o tym na końcu niniejszego podrozdziału). Bity CWR i ECE, używane do bezpośredniego powiadamiania o przeciążeniu, są opisane w punkcie 3.7.2. Ustawienie bitu PSH wskazuje, że odbiorca powinien natychmiast przekazać dane do wyżej położonej warstwy. Z kolei zadaniem bitu URG jest informowanie o tym, że w segmencie znajdują się dane, które jednostka górnej warstwy po stronie nadawczej oznaczyła jako „pilne”. Lokalizacja ostatniego bajta takich danych jest identyfikowana przez 16-bitowe pole wskaźnika „pilnych” danych. Po pojawieniu się „pilnych” danych protokół TCP musi poinformować o tym fakcie jednostkę górnej warstwy po stronie odbiorczej, a także przekazać jej wskaźnik do końca tych danych (w praktyce bity PSH i URG, jak również pole wskaźnika „pilnych” danych nie są wykorzystywane; wspomnieliśmy o tych polach, aby omówienie było kompletne).
Z naszego pedagogicznego doświadczenia wynika, że dla studentów omawianie formatów pakietów jest dość mało wciągające i prawdopodobnie nieco nudne. Zabawny i wyrafinowany przegląd pól nagłówka TCP znajdziesz w [Pomeranz 2010]; polecamy go osobom, które — podobnie jak my — uwielbiają klocki Lego.
Pola numerów sekwencyjnych i numerów potwierdzeń
Dwoma najważniejszymi polami segmentu TCP są pola numerów sekwencyjnych i numerów potwierdzeń. Mają one decydujące znaczenie dla usługi niezawodnego transferu danych. Jednak zanim wyjaśnimy, jak pola te są wykorzystywane przez tę usługę, przyjrzyjmy się najpierw temu, co właściwie protokół TCP umieszcza w tych polach.
Protokół TCP postrzega dane jako pozbawiony struktury, lecz uporządkowany strumień bajtów. Użycie przez protokół TCP numerów sekwencyjnych odzwierciedla takie traktowanie danych pod tym względem, że numery obowiązują dla strumienia transmitowanych bajtów, a nie dla serii przesyłanych segmentów. A zatem numer sekwencyjny segmentu jest numerem pierwszego bajta części strumienia zawartego w segmencie. Przytoczmy przykład. Załóżmy, że proces hosta A zamierza za pośrednictwem połączenia TCP wysłać strumień danych procesowi hosta B. Protokół TCP po stronie hosta A niejawnie nada numer każdemu bajtowi strumienia danych. Przyjmijmy, że strumień zawiera dane pliku liczącego 500 000 bajtów, wartością parametru MSS jest 1000 bajtów, natomiast pierwszemu bajtowi strumienia danych nadano numer zero. Jak widać na rysunku 3.30, protokół TCP tworzy ze strumienia 500 segmentów. Pierwszemu segmentowi jest przypisywany numer sekwencyjny 0, drugiemu numer 1000, trzeciemu numer 2000 itd. Każdy numer sekwencyjny jest umieszczany w polu numeru sekwencyjnego nagłówka odpowiedniego segmentu TCP.
Rysunek 3.30. Podzielenie danych pliku na segmenty TCP
Rozważmy teraz numery potwierdzeń. W porównaniu z numerami sekwencyjnymi są trochę bardziej złożone. Wcześniej wspomniano, że protokół TCP zapewnia usługę pełnego dupleksu. Dzięki temu host A może odbierać dane od hosta B podczas wysyłania do niego innych danych (w ramach tego samego połączenia TCP). Każdy z segmentów odsyłanych przez host B posiada numer sekwencyjny powiązany z danymi transmitowanymi od hosta B do hosta A. Numer potwierdzenia umieszczony przez host A w tworzonym przez niego segmencie jest numerem sekwencyjnym kolejnego bajta, którego host A oczekuje od hosta B. Aby dokładnie zrozumieć, o co w tym chodzi, warto przyjrzeć się kilku przykładom. Załóżmy, że host A otrzymał od hosta B wszystkie bajty ponumerowane od 0 do 535, a ponadto, że host A zamierza wysłać segment hostowi B. Host A oczekuje na bajt 536 i wszystkie kolejne zawarte w strumieniu danych przesyłanym przez host B. W związku z tym host A w polu numeru potwierdzenia segmentu wysyłanego do hosta B umieszcza liczbę 536.
W ramach kolejnego przykładu przyjmijmy, że host A otrzymał od hosta B jeden segment zawierający bajty od 0 do 535, a także kolejny segment przechowujący bajty od 900 do 1000. Z jakiegoś powodu host A nie dostał jeszcze bajtów z przedziału od 536 do 899. W tym przypadku host A nadal czeka na bajt 536 (i kolejne), aby móc odtworzyć strumień danych przesłany przez host B. A zatem następny segment wysłany przez host A do hosta B w polu numeru potwierdzenia będzie zawierał liczbę 536. Ponieważ protokół TCP potwierdza bajty jedynie do pierwszego brakującego w strumieniu, mówi się, że oferuje potwierdzenia skumulowane.
Ostatni przykład prezentuje ważną, choć subtelną kwestię. Host A odebrał trzeci segment (z bajtami od 900 do 1000) przed otrzymaniem drugiego segmentu (z bajtami od 536 do 899). A zatem trzeci segment dotarł poza kolejnością. Subtelną kwestią jest, jak postąpi host, gdy w ramach połączenia TCP odbierze segmenty niezachowujące kolejności wysłania? Interesujące jest to, że w dokumentach RFC poświęconych protokołowi TCP nie narzucono żadnych reguł i wybór pozostawiono osobom zajmującym się programowaniem implementacji protokołu. Zasadniczo można wyróżnić dwie możliwości — (1) odbiorca natychmiast odrzuci segmenty otrzymane poza kolejnością (ten omówiony wcześniej wariant pozwala uprościć architekturę odbiorcy) lub (2) zatrzyma takie segmenty i poczeka na brakujące bajty, aby móc wypełnić luki. Oczywiście gdy pod uwagę weźmie się przepustowość sieci, druga możliwość okaże się efektywniejsza i wykorzystywana w praktyce.
Na rysunku 3.30 przyjęliśmy, że początkowym numerem sekwencyjnym jest zero. W rzeczywistości obie strony połączenia TCP w losowy sposób ustalają numer sekwencyjny. Ma to na celu zminimalizowanie możliwości, że segment znajdujący się w sieci i powiązany z wcześniejszym już zakończonym połączeniem między dwoma hostami zostanie pomylony z właściwym segmentem nowszego połączenia nawiązanego między tymi samymi hostami, które również korzystają z identycznych portów [Sunshine 1978].
Telnet — analiza związana z numerami sekwencyjnymi i numerami potwierdzeń
Telnet zdefiniowany w dokumencie RFC 854 jest popularnym protokołem warstwy aplikacji umożliwiającym zdalne logowanie. Korzysta z protokołu TCP i funkcjonuje między parą dowolnych hostów. W przeciwieństwie do omówionych w rozdziale 2. aplikacji służących do transferu dużych ilości danych, Telnet jest narzędziem interaktywnym. W tym miejscu omówimy przykład zastosowania tego narzędzia, ponieważ świetnie ilustruje numery sekwencyjne i numery potwierdzenia protokołu TCP. Trzeba podkreślić, że obecnie wielu użytkowników zamiast z narzędzia Telnet woli korzystać z protokołu ssh. Wynika to stąd, że dane (z uwzględnieniem haseł!) przesyłane w ramach połączenia narzędzia Telnet nie są szyfrowane, przez co są narażone na ataki polegające na podsłuchu.
Przyjmijmy, że host A inicjuje sesję telnetową z hostem B. Ponieważ host A rozpoczął operację, jest klientem, natomiast host B jest serwerem. Każdy znak wprowadzony przez użytkownika klienta zostanie przesłany do zdalnego hosta, który odeśle kopię każdego znaku wyświetlonego w oknie sesji narzędzia Telnet. Odsyłanie ma na celu potwierdzenie, że znaki, które zobaczył użytkownik narzędzia, zostały otrzymane i przetworzone przez zdalny host. A zatem każdy znak dwukrotnie przemierza sieć od momentu wciśnięcia przez użytkownika klawisza do chwili wyświetlenia znaku na monitorze.
Załóżmy, że użytkownik wprowadzi literę C, a następnie zajmie się piciem kawy. Przyjrzyjmy się segmentom TCP przesyłanym między klientem i serwerem. Jak widać na rysunku 3.31, przyjęliśmy, że początkowymi numerami sekwencyjnymi używanymi przez klienta i serwer są odpowiednio 42 i 79. Wcześniej wspomniano, że numer sekwencyjny segmentu jest numerem powiązanym z pierwszym bajtem pola danych. A zatem pierwszy segment wysłany przez klienta będzie miał numer sekwencyjny 42. Z kolei pierwszy segment wysłany przez serwer będzie posiadał numer 79. Numer potwierdzenia jest numerem sekwencyjnym kolejnego bajta danych, którego oczekuje host. Po nawiązaniu połączenia TCP, lecz przed wysłaniem jakichkolwiek danych klient czeka na bajt 79, natomiast serwer na bajt 42.
Rysunek 3.31. Numery sekwencyjne i numery potwierdzeń prostej aplikacji telnetowej korzystającej z protokołu TCP
Na rysunku 3.31 widać, że są wysyłane trzy segmenty. Pierwszy segment jest przesyłany od klienta do serwera i w polu danych zawiera jednobajtową reprezentację ASCII dla litery C. Pierwszy segment posiada też numer 42 umieszczony w polu numeru sekwencyjnego, które właśnie omówiliśmy. Ponadto, ze względu na to, że klient nie otrzymał jeszcze od serwera żadnych danych, w polu numeru potwierdzenia pierwszego segmentu będzie znajdowała się liczba 79.
Drugi segment jest transmitowany od serwera do klienta. Służy on dwóm celom. Po pierwsze, zapewnia potwierdzenie odebrania danych przez serwer. Umieszczając liczbę 43 w polu numeru potwierdzenia, serwer informuje klienta o tym, że z powodzeniem odebrał wszystkie bajty aż do bajta 42 i oczekuje na bajty numerowane począwszy od 43. Po drugie, celem tego segmentu jest odesłanie litery C. A zatem drugi segment w swoim polu danych przechowuje reprezentację ASCII dla litery C. Segment posiada też numer sekwencyjny 79, który jest początkowym numerem sekwencyjnym związanym z przesyłaniem w ramach połączenia TCP danych od serwera do klienta (numerem dysponuje pierwszy bajt danych transmitowanych przez serwer). Warto zauważyć, że potwierdzenie danych przesłanych serwerowi przez klienta jest zawarte w segmencie transferującym dane od serwera do klienta. Mówi się, że potwierdzenie to jest dostawione do segmentu danych wysłanego przez serwer klientowi.
Trzeci segment jest przesyłany od klienta do serwera. Jego jedynym celem jest potwierdzenie otrzymania danych wysłanych przez serwer (drugi segment zawierał dane, czyli literę C odesłaną klientowi przez serwer). Pole danych tego segmentu jest puste (oznacza to, że potwierdzenie nie jest dostawiane do danych przesyłanych od klienta do serwera). W polu numeru potwierdzenia segment ma liczbę 80. Wynika to stąd, że klient odebrał strumień bajtów o numerach sekwencyjnych aż do 79 i oczekuje na bajty mające numery począwszy od 80. Biorąc pod uwagę fakt, że segment nie zawiera danych, za dziwne można uznać to, że segment posiada numer sekwencyjny. Ze względu na to, że w segmencie TCP znajduje się pole numeru sekwencyjnego, musi w nim zostać umieszczona jakaś liczba.
Opracowano na podstawie: