Поле Checksum служит для повышения надежности. Как и в UDP, оно содержит контрольную сумму заголовка, данных и псевдозаголовка. Но в отличие от UDP псевдозаголовок содержит номер протокола TCP (6), а контрольная сумма является обязательной. Более подробная информация приведена в разделе 6.4.1.
Поле Options предоставляет дополнительные возможности, не покрываемые стандартным заголовком. Существует множество параметров, и некоторые из них широко используются. Они имеют разную длину, кратную 32 битам (лишнее место заполняется нулями), и могут доходить до отметки в 40 байт — максимального размера заголовка TCP. При установлении соединения факультативные поля могут использоваться для того, чтобы договориться с противоположной стороной или просто сообщить ей о характеристиках этого соединения. Существуют поля, сохраняющиеся в течение всего времени жизни соединения. Все факультативные поля имеют формат Тип-Длина-Значение (Type-Length-Value).
С помощью одного из таких полей хост может указать максимальный размер сегмента (Maximum Segment Size, MSS), который он может принять. Чем больше размер, тем выше эффективность, так как при этом снижается удельный вес накладных расходов в виде 20-байтных заголовков, однако не все хосты способны принимать крупные сегменты. Хосты могут сообщить друг другу MSS во время установки соединения. По умолчанию он равен 536 байтам. Все хосты обязаны принимать TCP-сегменты размером 536 + 20 = 556 байт. Для каждого направления можно установить свой MSS.
Для линий с высокой скоростью передачи и/или большой задержкой окно в 64 Кбайт, соответствующее 16-битному полю, оказывается слишком маленьким. Так, на линии OC-12 (со скоростью приблизительно 600 Мбит/с) для вывода полного окна в 64 Кбайт потребуется менее 1 мс. Если время распространения сигнала в оба конца составляет 50 мс (что типично для трансконтинентального оптического кабеля), 98 % времени отправитель будет ждать подтверждения. Больший размер окна мог бы повысить эффективность. Параметр масштаб окна (window scale) позволяет двум хостам договориться о масштабе окна при установке соединения. С его помощью стороны могут сдвигать поле Window size до 14 разрядов влево, расширяя окна до 230 байт (1 Гбайт). Большинство реализаций TCP поддерживают эту возможность.
Для временных меток (timestamps), передаваемых от отправителя к получателю и обратно, существует одноименный параметр. Если во время настройки соединения было решено использовать этот параметр, он добавляется в каждый пакет. Он позволяет получить данные об RTT, необходимые для выявления потери пакетов. Также он используется в качестве логического расширения 32-битного порядкового номера. При высокоскоростном соединении порядковые номера могут проходить полный круг очень быстро, и в результате новые и старые данные будет невозможно отличить. Описанная ранее схема PAWS удаляет сегменты со старыми временными метками, позволяя избежать этой проблемы.
Наконец, с помощью выборочного подтверждения (Selective ACKnowledgement, SACK) получатель может сообщать отправителю диапазоны порядковых номеров доставленных пакетов. Этот параметр является дополнением к Acknowledgement number и используется, если после потери пакета данные все равно были доставлены (возможно, в виде копии). Новые данные не отражены в поле заголовка Acknowledgement number, так как оно содержит только следующий по порядку ожидаемый байт. Благодаря SACK отправитель всегда будет знать, какие данные есть у получателя, и повторит передачу, только если это действительно нужно. SACK описан в RFC 2108 и RFC 2883. В последнее время эта схема используется все чаще. О ее применении при контроле перегрузки мы поговорим в разделе 6.5.10.
6.5.5. Установка TCP-соединения
В TCP соединения устанавливаются с помощью «тройного рукопожатия», как было описано в разделе 6.2.2. Чтобы создать соединение, одна сторона (например, сервер) пассивно ожидает входящего соединения, выполняя примитивы LISTEN и ACCEPT с указанием конкретного источника либо без него.
Другая сторона (например, клиент) выполняет примитив CONNECT, сообщая IP-адрес и порт, с которым она хочет установить соединение, максимальный размер TCP-сегмента, который она может принять, и, по желанию, некоторые данные пользователя (например, пароль). CONNECT отправляет TCP-сегмент с установленным битом SYN и сброшенным битом ACK и ждет ответа от другой стороны.
Когда этот сегмент приходит по назначению, TCP-подсистема проверяет, выполнил ли какой-нибудь процесс примитив LISTEN, указав в качестве параметра тот же порт, который содержится в поле Destination port. Если такого процесса нет, она отвечает отправкой сегмента с установленным битом RST для отказа от соединения.
Если какой-то процесс прослушивает указанный порт, то TCP-сегмент передается этому процессу. Он может принять соединение или отказаться от него. Если процесс принимает соединение, он отвечает подтверждением. Последовательность TCP-сегментов, отправляемых в обычном случае, показана на илл. 6.37 (а). Обратите внимание, что сегмент с установленным битом SYN занимает 1 байт пространства порядковых номеров, что позволяет избежать неоднозначности в их подтверждениях.
Илл. 6.37. (а) Установка TCP-соединения в обычном случае. (б) Одновременная установка соединения обеими сторонами
Когда два хоста одновременно пытаются установить соединение друг с другом, то события происходят в иной последовательности (см. илл. 6.37 (б)). В результате будет установлено только одно соединение, а не два, так как соединения идентифицируются по паре конечных точек. То есть если они оба обозначают себя с помощью пары (x, y), делается всего одна табличная запись (x, y).
Начальное значение порядкового номера, выбранное каждым хостом, должно медленно меняться, а не равняться константе (например, нулю). Как мы уже говорили в разделе 6.2.2, это правило обеспечивает защиту от задержавшихся копий пакетов. Изначально эта схема была реализована с помощью таймера, изменяющего свое состояние каждые 4 мкс.
Однако проблема реализации схемы «тройного рукопожатия» состоит в том, что слушающий процесс должен помнить свой порядковый номер до тех пор, пока он не отправит собственный SYN-сегмент. Это значит, что злонамеренный отправитель может блокировать ресурсы хоста, отправляя на него поток SYN-сегментов и не разрывая соединение. Такие атаки называются лавинной адресацией SYN-сегментов (SYN flood). В 1990-е годы многие веб-серверы оказались парализованными из-за них. Сегодня уже существуют методы защиты от таких атак.
В частности, для защиты от них можно использовать метод под названием SYN cookies. Вместо того чтобы запоминать порядковый номер, хост генерирует криптографическое значение номера, записывает его в исходящий сегмент и забывает. Если «тройное рукопожатие» завершается, этот номер (увеличенный на единицу) вернется на хост. Хост может повторно сгенерировать правильный порядковый номер, вычислив значение той же криптографической функции, при условии, что известны входные данные (это может быть IP-адрес и порт другого хоста, а также какое-то секретное значение). С помощью этой процедуры хост может проверять правильность подтвержденного порядкового номера, не запоминая его. Одна из тонкостей этого метода состоит в том, что он не работает с дополнительными параметрами TCP. Поэтому SYN cookies можно использовать только в случае лавинной адресации SYN-сегментов. Но в целом это очень интересный прием. Более подробно см. RFC 4987 и работу Лемона (Lemon, 2002).
6.5.6. Разрыв TCP-соединения
Хотя TCP-соединения полнодуплексные, чтобы понять, как происходит их разъединение, лучше считать их парами симплексных соединений. Каждое симплексное соединение разрывается независимо от своего напарника. Чтобы его разорвать, любая из сторон может отправить TCP-сегмент с установленным битом FIN; это означает, что у него больше нет данных для передачи. После подтверждения TCP-сегмента это направление закрывается. Тем не менее данные могут продолжать передаваться неопределенно долго в противоположную сторону. Соединение разрывается, когда закрываются оба направления. Обычно для разрыва требуются четыре TCP-сегмента: по одному с битом FIN и по одному с битом ACK в каждом направлении. Первый бит ACK и второй бит FIN могут также содержаться в одном TCP-сегменте, что уменьшит количество сегментов до трех.
Как при телефонном разговоре, когда оба участника могут одновременно попрощаться и повесить трубку, оба конца TCP-соединения могут отправить FIN-сегменты в одно и то же время. Они оба получают обычные подтверждения, и соединение закрывается. По сути, между одновременным и последовательным разъединением нет никакой разницы.
Чтобы избежать проблемы «двух армий» (см. раздел 6.2.3), используются таймеры. Если ответ на отправленный FIN-сегмент не приходит в течение двух максимальных интервалов времени жизни пакета, отправитель FIN-сегмента разрывает соединение. Другая сторона в конце концов заметит, что ей никто не отвечает, и также отсоединится. Эта схема несовершенна, однако, учитывая недостижимость идеала, приходится пользоваться тем, что есть. На практике проблемы возникают довольно редко.
6.5.7. Модель управления TCP-соединением
Этапы, необходимые для установления и разрыва соединения, могут быть представлены в виде модели конечного автомата, 11 состояний которого перечислены на илл. 6.38. В каждом из этих состояний разрешены определенные события, в ответ на которые могут осуществляться действия. При возникновении каких-либо других событий сообщается об ошибке.
Каждое соединение начинается в состоянии CLOSED (закрыто). Оно может покинуть это состояние, предпринимая либо активную (CONNECT), либо пассивную (LISTEN) попытку открыть соединение. Если другая сторона осуществляет противоположное действие, соединение устанавливается и переходит в состояние ESTABLISHED. Инициатором разрыва соединения может выступить любая сторона. По завершении этого процесса соединение возвращается в состояние CLOSED.