TCP-IP крупным планом

       

Состояние ожидания 2MSL



Состояние ожидания 2MSL

Состояние ВРЕМЯ_ОЖИДАНИЯ (TIME_WAIT) также иногда называется состоянием ожидания 2MSL. В каждой реализации выбирается значение для максимального времени жизни сегмента (MSL - maximum segment lifetime) . Это максимальное время, в течение которого сегмент может существовать в сети, перед тем как он будет отброшен. Мы знаем, что это время ограничено, так как TCP сегменты передаются посредством IP датаграмм, а каждая IP датаграмма имеет поле TTL, которое ограничивает время ее жизни.

RFC 793 [Postel 1981c] указывает, что MSL должно быть равно 2 минутам. В разных реализациях эта величина имеет значение 30 секунд, 1 минута или 2 минуты.

В главе 8 говорилось, что время жизни IP датаграммы ограничивается количеством пересылок, а не таймером.

При использовании MSL действуют следующие правила: когда TCP осуществляет активное закрытие и посылает последний сегмент содержащий подтверждение (ACK), соединение должно остаться в состоянии TIME_WAIT на время равное двум MSL. Это позволяет TCP повторно послать последний ACK в том случае, если первый ACK потерян (в этом случае удаленная сторона отработает тайм-аут и повторно передаст свой конечный FIN).

Другое назначение ожидания 2MSL заключается в том, что пока TCP соединение находится в ожидании 2MSL, пара сокетов, выделенная для этого соединения (IP адрес клиента, номер порта клиента, IP адрес сервера и номер порта сервера), не может быть повторно использована. Это соединение может быть использовано повторно только когда истечет время ожидания 2MSL.

К сожалению, большинство реализаций (Berkeley одна из них) подчиняются более жестким требованиям. По умолчанию локальный номер порта не может быть повторно использован, до тех пор пока этот номер порта является локальным номером порта пары сокетов, который находится в состоянии ожидания 2MSL. Ниже мы рассмотрим примеры общих требований.

Некоторые реализации и API предоставляют средства, которые позволяют обойти эти ограничения. С использованием API сокет может быть указана опция сокета SO_REUSEADDR. Она позволяет вызывающему назначить себе номер локального порта, который находится в состоянии 2MSL, однако мы увидим, что правила TCP не позволяют этому номеру порта быть использованным в соединении, которое находится в состоянии ожидания 2MSL.

Каждый задержанный сегмент, прибывающий по соединению, которое находится в состоянии ожидания 2MSL, отбрасывается. Так как соединение определяется парой сокет в состоянии 2MSL, это соединение не может быть повторно использовано до того момента, пока мы не сможем установить новое соединение. Это делается для того, чтобы опоздавшие пакеты не были восприняты как часть нового соединения. (Соединение определяется парой сокет. Новое соединение называется восстановлением или оживлением данного соединения.)

Как мы уже показали на рисунке 18.13, обычно клиент осуществляет активное закрытие и входит в режим TIME_WAIT. Сервер обычно осуществляет пассивное закрытие и не проходит через режим TIME_WAIT. Можно сделать вывод, что если мы выключим клиента и немедленно его перестартуем, этот новый клиент не сможет использовать тот же самый локальный номер порта. В этом нет никакой проблемы, так как клиенты обычно используют динамически назначаемые порты и не заботятся, какой динамически назначаемый порт используется в настоящее время.

Однако, с точки зрения сервера все иначе, так как сервера используют заранее известные порты. Если мы выключим сервер, который имеет установленное соединение, и постараемся немедленно перестартовать его, сервер не может использовать свой заранее известный номер порта в качестве конечной точки соединения, так как этот номер порта является частью соединения, находящегося в состоянии ожидания 2MSL. Поэтому может потребоваться от 1 до 4 минут, перед тем как сервер будет перестартован.

Пронаблюдать подобный сценарий можно с использованием программы sock. Мы стартовали сервер, подсоединили к нему клиента, а затем выключили сервер:


sun % sock -v -s 6666 стартуем сервер, слушающий порт 6666
(запускаем клиента на bsdi, который подсоединится к этому порту)
connection on 140.252.13.33.6666 from140.252.13.35.1081
^? вводим символ прерывания, чтобы выключить сервер
sun % sock -s 6666 и стараемся немедленно перестартовать сервер на тот же самый порт
can't bind local address: Address already in use
sun % netstat попробуем проверить состояние соединения
Active Internet сonnections
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp 0 0 sun.6666 bsdi.1081 TIME_WAIT
множество строк удалено

Когда мы стараемся перестартовать сервер, программа выдает сообщение об ошибке, указывающее на то, что она не может захватить свой заранее известный номер порта, потому что он уже используется (находится в состоянии ожидания 2MSL).



Затем мы немедленно исполняем netstat, чтобы посмотреть состояние соединения и проверить, что оно действительно находится в состоянии TIME_WAIT.

Если мы будем продолжать попытки перестартовать сервер и посмотрим время, когда это удастся, то можем вычислить значение 2MSL. Для SunOS 4.1.3, SVR4, BSD/386 и AIX 3.2.2 перестартовка сервера займет 1 минуту, что означает, что MSL равно 30 секундам. В Solaris 2.2 эта перестартовка сервера занимает 4 минуты, это означает, что MSL равно 2 минутам.

Мы можем увидеть ту же самую ошибку, сгенерированную клиентом, если клиент старается захватить порт, который является частью соединения, находящего в режиме ожидания 2MSL (обычно клиент этого не делает):


sun % sock -v bsdi echo стартуем клиент, который подсоединяется к серверу echo
connected on 140.252.13.33.1162 to 140.252.13.35.7
hello there печатаем эту строку
hello there она отражается эхом от сервера
^D вводим символ конца файла, чтобы выключить клиента
sun % sock -b1162 bsdi echo
can't bind local address: Address already in use

При первом запуске клиента была указана опция -v, которая позволяет посмотреть, какой используется локальный номер порта (1162). При втором запуске клиента, была указана опция -b, которая сообщает клиенту о необходимости назначить самому себе номер локального порта 1162. Как мы и ожидали, клиент не может этого сделать, так как этот номер порта является частью соединения, которое находится в состоянии 2MSL.

Здесь необходимо упомянуть об одной особенности состояния ожидания 2MSL, к которой мы вернемся в главе 27, когда будем рассказывать о протоколе передачи файлов (FTP - File Transfer Protocol). Как уже упоминалось раньше, в состоянии ожидания 2MSL остается пара сокетов (состоящая из локального IP адреса, локального порта, удаленного IP адреса и удаленного порта). Однако, множество реализаций позволяют процессу повторно использовать номер порта, который является частью соединения, находящегося в режиме 2MSL (обычно с использованием опции SO_REUSEADDR) , TCP не может позволить создать новое соединение с той же самой парой сокет. Это можно доказать с помощью следующего эксперимента:


sun % sock -v -s 6666 старт сервера, слушающего порт 6666
(запускаем клиента на bsdi, который подсоединяется к этому порту)
connection on 140.252.13.33.6666 from 140.252.13.35.1098
^? вводим символ прерывания, чтобы выключить сервер
sun % sock -b6666 bsdi 1098 стартуем клиента с локальным портом 6666
can't bind local address: Address already in use
sun % sock -A -b6666 bsdi 1098 пытаемся снова, на этот раз с опцией -A
active open error: Address already in use

В первый раз мы запустили нашу программу sock как сервер на порт 6666 и подсоединили к нему клиента с хоста bsdi. Номер динамически назначаемого порта клиента 1098. Мы выключили сервер, таким образом, он осуществил активное закрытие. При этом 4 параметра - 140.252.13.33 (локальный IP адрес), 6666 (локальный номер порта), 140.252.13.35 (удаленный IP адрес) и 1098 (удаленный номер порта) на сервере попадают в состояние 2MSL.

Во второй раз мы запустили эту программу в качестве клиента, указав локальный номер порта 6666, при этом была сделана попытка подсоединиться к хосту bsdi на порт 1098. При попытке повторно использовать локальный порт 6666 была сгенерирована ошибка, так как этот порт находится в состоянии 2MSL.

Чтобы избежать появления этой ошибки, мы запустили программу снова, указав опцию -A, которая активизирует опцию SO_REUSEADDR. Это позволило программе назначить себе номер порта 6666, однако была получена ошибка, когда программа попыталась осуществить активное открытие. Даже если программа сможет назначить себе номер порта 6666, она не сможет создать соединения с портом 1098 на хосте bsdi, потому что пара сокетов, определяющая это соединение, находится в состоянии ожидания 2MSL.

Что если мы попытаемся установить соединение с другого хоста? Во-первых, мы должны перестартовать сервер на sun с флагом -A, так как порт, который ему необходим (6666), является частью соединения, находящегося в состоянии ожидания 2MSL:


sun % sock -A -s 6666 стартуем сервер, слушающий порт 6666

Затем, перед тем как состояние ожидания 2MSL закончится на sun, мы стартуем клиента на bsdi:

bsdi % sock -b1098 sun 6666
connected on 140.252.13.35.1098 to 140.252.13.33.6666

К сожалению, это работает! Это является недостатком TCP спецификации, однако поддерживается большинством реализаций Berkeley. Эти реализации воспринимают прибытие запроса на новое соединение для соединения, которое находится в состоянии TIME_WAIT, если новый номер последовательности больше чем последний номер последовательности, использованный в предыдущем соединении. В этом случае ISN для нового соединения устанавливается равным последнему номеру последовательности для предыдущего соединения плюс 128000. Приложение к RFC 1185 [Jacobson, Braden, and Zhang 1990] показывает возможные недостатки подобной технологии.

Эта характеристика реализации позволяет клиенту и серверу повторно использовать те же самые номера порта для успешного восстановления того же самого соединения, в том случае, однако, если сервер не осуществил активное закрытие. Мы увидим другой пример состояния ожидания 2MSL на рисунке 27.8, когда будем обсуждать FTP. Также обратитесь к упражнению 5 этой главы.



Содержание раздела