Глава 23 TCP таймер "оставайся в живых"
Большинство новичков в TCP/IP, как правило, бывают очень удивлены, когда узнают, что по свободному TCP соединению не передаются данные. А это именно так, то есть если ни один из процессов на концах TCP соединения не посылает данные другому процессу, обмена между двумя TCP модулями не осуществляется. Например, не осуществляется опросов, как это происходит в других сетевых протоколах. Другими словами, мы можем запустить процесс клиента, который установит TCP соединение с сервером, а затем уйти на несколько часов, дней, недель или месяцев, а соединение будет держаться. Промежуточные маршрутизаторы могут выходить из строя и перезагружаться, телефонные линии могут обрыватьься и восстанавливаться, однако если хосты на концах соединения не будут перезагружены, соединение будет оставаться установленным.
При этом подразумевается, что ни одно из приложений - клиент или сервер - не имеет таймеров на прикладном уровне, которые позволяют определить отсутствие активности по соединению, и прекратить работу приложения. Обратитесь к концу раздела "BGP: протокол граничных маршрутизаторов" главы 10, где показано, что BGP посылает пробы приложениям на удаленном конце каждые 30 секунд. Это прикладной таймер, который действует независимо от TCP таймера "оставайся в живых".
Однако существуют моменты, когда сервер хочет узнать, что случилось с хостом клиента: или он вышел из строя и был выключен, или вышел из строя и перезагрузился. Таймер "оставайся в живых" (keepalive timer) это характеристика большинства реализаций, которая предоставляет эту возможность.
Таймеры "оставайся в живых" не являются частью TCP спецификации. Требования к хостам Host Requirements RFC приводят три причины, по которым их не следует использовать: (1) они могут привести к тому, что абсолютно нормальное соединение будет разорвано из-за непродолжительного сбоя, (2) они занимают определенную ширину полосы, и (3) они стоят денег, так как обмен пакетами между сетями имеет определенную цену. Тем не менее, большинство реализаций имеют таймер "оставайся в живых".
Необходимость иметь таймер "оставайся в живых" все еще обсуждается. Многие считают, что подобный опрос удаленного конца не свойственен TCP и должен по необходимости осуществляться приложением. От подобных заявлений отдает религией, в основном из-за того, что они декларируются очень эмоционально и с большим жаром.
Опция "оставайся в живых" может вызвать разрыв устойчивого соединения между двумя процессами из-за временной потери соединения между двумя конечными системами. Например, если проба "оставайся в живых" отправлена в тот момент, когда промежуточный маршрутизатор вышел из строя и перезагружается, TCP подумает, что вышел из строя хост клиента, что, естественно, не так.
Характеристика "оставайся в живых" предназначена для того, чтобы приложение сервера могло оценить поведение клиентов и имело возможность определить, что клиент вышел из строя. Большинство версий Telnet и Rlogin серверов по умолчанию включают опцию "оставайся в живых".
Можно привести пример, однозначно доказывающий необходимость характеристики "оставайся в живых". Пользователи персональных компьютеров часто заходят терминалами на хост с помощью Telnet. В конце рабочего дня они просто выключают питание компьютера, не закрыв соединения. При этом остается полуоткрытое соединение. На рисунке 18.16 мы показали, что отправка данных по полуоткрытому соединению приводит к возврату сброса (reset), однако это происходит в том случае когда данные отправляются клиентом. Если клиент исчез, оставив полуоткрытое соединение на конце сервера, а сервер ожидает каких-либо данных от клиента, он будет ждать вечно. Характеристика "оставайся в живых" предназначена для того, чтобы помочь серверу определить наличие полуоткрытых соединений.
В этом описании мы будем называть конец, на котором включается опция "оставайся в живых", сервером, а другой конец - клиентом. Ничто не запрещает клиенту установить эту опцию, однако она устанавливается именно на сервере. Опция может быть установлена на обоих концах соединения, если каждому концу необходимо знать, работает ли удаленный конец. (В главе 29 мы увидим, что когда NFS использует TCP, оба, и клиент, и сервер, устанавливают эту опцию. А в главе 26, когда мы будем рассматривать Rlogin и Telnet, мы увидим, что эту опцию устанавливает только сервер, но не клиент.)
Если в заданном соединении не осуществляются какие-либо действия в течение 2 часов, сервер отправляет клиенту пробный сегмент. (Мы увидим, что представляет из себя пробный сегмент, в примере, который приведен ниже.) Хост клиента должен находиться в одном из четырех состояний.
Сервер не должен беспокоиться о том, что хост клиента был выключен и затем перезагружен. (Имеется ввиду shutdown, а не выход хоста из строя.) Когда система выключена оператором, все процессы клиента корректно завершают свою работу, при этом TCP клиент отправляет FIN для соединения. При получении FIN, TCP сервер выдает метку конца файла процессу сервера, что позволяет серверу корректно закрыть соединение.
В первом сценарии приложение сервера не подозревает о том, что были отправлены пробы "оставайся в живых". Все это делается на TCP уровне. Для приложения абсолютно безразлично, имели ли место сценарии 2, 3 или 4. Во всех трех случаях приложению сервера возвращается ошибка от его собственного TCP. (Обычно сервер осуществляет чтение из сети, ожидая данные от клиента. Если характеристика "оставайся в живых" возвратила ошибку, она передается серверу как код возврата на операцию чтения.) В случае второго сценария ошибка выглядит примерно следующим образом: "соединение закрыто по тайм-ауту" (connection timed out), а в случае третьего сценария мы можем ожидать "соединение сброшено удаленным концом" (connection reset by peer). Четвертый сценарий может выглядеть как, если соединение разорвано по тайм-ауту, однако может быть возвращена и другая ошибка, в зависимости от того, какая принята ICMP ошибка по этому соединению. Мы рассмотрим все четыре сценария в следующих разделах.
Вечный вопрос, который задают люди, изучающие опцию "оставайся в живых", заключается в том, может ли быть изменено значение 2-часового тайм-аута. Обычно требуется значительно меньше времени, где-то несколько минут. Как мы показали в приложении Е, это значение обычно может быть изменено, однако во всех системах, описанных в этом приложении, интервал "оставайся в живых" является системным значением, поэтому его изменение окажет влияние на всех пользователей.
Требования к хостам Host Requirements RFC говорят, что реализации могут иметь характеристику "оставайся в живых", однако она не должна включаться, за исключением тех случаев, когда приложение специально требует это. Более того, интервал "оставайся в живых" должен быть конфигурируемым, однако по умолчанию он должен быть не меньше чем 2 часа.
Примеры "оставайся в живых"
Сейчас мы просмотрим сценарии 2, 3 и 4 из предыдущего раздела, чтобы рассмотреть обмен пакетами при использовании опции "оставайся в живых".
Удаленный конец вышел из строя
Давайте посмотрим, что произойдет, когда хост сервера вышел из строя и не перезагрузился. Чтобы имитировать эту ситуацию, мы поступим следующим образом:
Здесь приводится интерактивный вывод клиента:
bsdi % sock -K svr4 echo
опция -K
для включения "оставайся в живых"
hello, world
убедимся,
что соединение работает
hello, world
отражение
эхом
отсоединяем
Ethernet кабель после 4 часов
read error: Connection timed out это произошло
примерно через 6 часов 10 минут после начала
эксперимента
На рисунке 23.1 показан вывод tcpdump. (Мы удалили все посвященное установлению соединения и объявлению окна.)
1 0.0
bsdi.1055
> svr4.echo: P 1:14(13)ack 1
2 0.006105 ( 0.0061) svr4.echo >
bsdi.1055: P 1:14(13)ack14
3 0.093140 ( 0.0870) bsdi.1055 >
svr4.echo: . ack 14
4 7199.972793 (7199.8797) arp who-has svr4 tell bsdi
5 7199.974878 ( 0.0021) arp reply svr4 is-at
0:0:c0:c2:9b:26
6 7199.975741 ( 0.0009) bsdi.1055 > svr4.echo: .
ack 14
7 7199.979843 ( 0.0041) svr4.echo > bsdi.1055: .
ack 14
8 14400.134330 (7200.1545) arp who-has svr4 tell bsdi
9 14400.136452 ( 0.0021) arp reply svr4 is-at
0:0:c0:c2:9b:26
10 14400.137391 ( 0.0009) bsdi.1055 > svr4.echo: . ack 14
11 14400.141408 ( 0.0040) svr4.echo > bsdi.1055: . ack 14
12 21600.318309 (7200.1769) arp who-has svr4 tell bsdi
13 21675.320373 ( 75.0021) arp who-has svr4 tell bsdi
14 21750.322407 ( 75.0020) arp who-has svr4 tell bsdi
15 21825.324460 ( 75.0021) arp who-has svr4 tell bsdi
16 21900.436749 ( 75.1123) arp who-has svr4 tell bsdi
17 21975.438787 ( 75.0020) arp who-has svr4 tell bsdi
18 22050.440842 ( 75.0021) arp who-has svr4 tell bsdi
19 22125.432883 ( 74.9920) arp who-has svr4 tell bsdi
20 22200.434697 ( 75.0018) arp who-has svr4 tell bsdi
21 22275.436788 ( 75.0021) arp who-has svr4 tell bsdi
Рисунок 23.1 Пакеты "оставайся в живых", которые определяют, что хост вышел из строя.
В строках 1, 2 и 3 отправляется строка "hello, world" от клиента к серверу и обратно. Первая проба "оставайся в живых" появляется через 2 часа (7200 секунд) в строке 4. Первое на что необходимо обратить внимание - это ARP запрос и ARP отклик перед отправкой TCP сегмента в строке 6. На пробу "оставайся в живых" в строке 6 приходит отклик с удаленного конца (строка 7). Тот же обмен пакетами происходит через 2 часа в строках 8-11.
Если бы мы могли видеть все поля в пробах "оставайся в живых" (строки 6 и 10), то обязательно обратили бы внимание на то, что поле номера последовательности на единицу меньше чем следующий отправляемый номер последовательности, который должен быть отправлен (в данном примере 13, тогда как должен быть 14). Так как в сегменте нет данных, tcpdump не печатает поле номера последовательности. (Программа tcpdump печатает номер последовательности для пустых сегментов, только в том случае если они содержат флаги SYN, FIN или RST.) Именно прием этих неверных номеров последовательности заставляет TCP модуль сервера отвечать подтверждениями на пробы "оставайся в живых". В отклике клиенту сообщается следующий номер последовательности, которую ожидает сервер (14).
Некоторые более старые реализации, основанные на 4.2BSD, не отвечают откликом на пробы "оставайся в живых", если сегмент не содержит данных. Некоторые системы могут быть сконфигурированы так, чтобы посылать в пробе один байт данных, чтобы получить на него отклик. Этот байт не принесет никакого вреда, потому что это не ожидаемый байт (байт, который получатель уже ранее получил и подтвердил), поэтому он отбрасывается получателем. Другие системы посылают сначала сегмент в стиле 4.3BSD (без данных) в качестве пробы, и если отклик не получен, переключаются на сегменты в стиле 4.2BSD.
Затем мы отсоединили кабель и ожидаем, что на следующую пробу (а именно через 2 часа) отклик не будет получен. Когда появляется следующая проба, мы никогда не увидим TCP сегменты в кабеле, потому что хост не отвечает на ARP запросы. Все же мы видим, что клиент отправляет 10 проб, с промежутком в 75 секунд, перед тем как прекратить попытки. Из нашего интерактивного скрипта мы видим, что код ошибки, возвращенный процессу клиента от TCP модуля, транслируется в сообщение "Connection timed out" (соединение закрыто по тайм-ауту).
Удаленный конец вышел из строя и перезагрузился
В этом примере мы увидим, что произойдет, если сервер выйдет из строя и перезагрузится. Первоначальный сценарий такой же как и раньше, однако после того, как мы убедились, что соединение функционирует, мы отсоединили сервер от Ethernet, перезагрузили его и затем вновь подсоединили его к Ethernet. Мы ожидаем, что следующая проба "оставайся в живых" сгенерирует сброс (reset) от сервера, потому что сервер сейчас ничего не знает об этом соединении. Ниже приводится интерактивная сессия:
bsdi % sock -K svr4 echo
опция
-K для включения "оставайся в живых"
hi there
проверяем,
что соединение функционирует
hi there
это
отражается эхом с удаленного конца
здесь
сервер перезагружен, пока отсоединен кабель Ethernet
read error: Connection reset by peer
На рисунке 23.2 показан вывод команды tcpdump. (Мы удалили все связанное с установлением соединения и объявлением окна.)
1 0.0
bsdi.1057
> svr4.echo: P 1:10(9) ack 1
2 0.006406 ( 0.0064) svr4.echo
> bsdi.1057: P 1:10(9) ack 10
3 0.176922 ( 0.1705) bsdi.1057
> svr4.echo: . ack 10
4 7200.067151 (7199.8902) arp who-has svr4 tell bsdi
5 7200.069751 ( 0.0026) arp reply svr4 is-at
0:0:c0:c2:9b:26
6 7200.070468 ( 0.0007) bsdi.1057 >
svr4.echo: . ack 10
7 7200.075050 ( 0.0046) svr4.echo >
bsdi.1057: R 1135563275:1135563275(0)
Рисунок 23.2 Пример "оставайся в живых", когда удаленный хост вышел из строя и перезагрузился.
Мы установили соединение и послали 9 байт данных от клиента к серверу (строки 1-3). Два часа спустя клиент отправил первую пробу "оставайся в живых", отклик от сервера содержит сброс. Приложение клиента печатает сообщение об ошибке "Connection reset by peer" (соединение сброшено удаленным концом).
Удаленный конец недоступен
В этом примере сервер не выходит из строя, однако он недоступен в течение 10-минутного периода, когда отправляются пробы "оставайся в живых". Вполне возможно, что вышел из строя промежуточный маршрутизатор, телефонная линия может быть временно повреждена или произошло что-нибудь подобное.
Чтобы имитировать эту ситуацию, мы установили TCP соединение с нашего хоста slip через SLIP канал с дозвоном на хост vangogh.cs.berkeley.edu, а затем погасили канал. Во-первых, приведем вывод интерактивной сессии:
slip % sock -K vangogh.cs.berkeley.edu echo
testing
вводим
эту строку
testing
строка
отражена эхом
где-то
в это время погашен SLIP канал
read error: No route to host
На рисунке 23.3 показан вывод команды tcpdump, который получен с маршрутизатора bsdi. (Установление соединения и объявления окна удалены.)
1 0.0
slip.1056
> vangogh.echo: P 1:9(8)ack1
2 0.277669 ( 0.2777)
vangogh.echo > slip.1056: P 1:9(8)ack9
3 0.424423 ( 0.1468) slip.1056
> vangogh.echo: . ack 9
4 7200.818081 (7200.3937) slip.1056 > vangogh.echo: . ack
9
5 7201.243046 ( 0.4250) vangogh.echo >
slip.1056: . ack 9
6 14400.688106 (7199.4451) slip.1056 > vangogh.echo: . ack 9
7 14400.689261 ( 0.0012) sun > slip: icmp: net
vangogh unreachable
8 14475.684360 ( 74.9951) slip.1056 > vangogh.echo: . ack
9
9 14475.685504 ( 0.0011) sun > slip: icmp: net
vangogh unreachable
14 строк удалено
24 15075.759603 ( 75.1008) slip.1056 > vangogh.echo: R
9:9(0)ack9
25 15075.760761 ( 0.0012) sun > slip: icmp: net
vangogh unreachable
Рисунок 23.3 Пример "оставайся в живых", когда удаленный конец недоступен.
Мы начинаем этот пример так же, как и предыдущий: в строках 1-3 убеждаемся, что соединение функционирует. На первую пробу "оставайся в живых", отправляемую через 2 часа, успешно получен отклик (строки 4 и 5), однако перед тем, как будет отправлена следующая, еще через 2 часа, мы выключили SLIP соединение между маршрутизаторами sun и netb. (Обратитесь к топологии, приведенной на внутренней стороне обложки.)
На пробу "оставайся в живых" в строке 6 генерируется ICMP ошибка о недоступности сети от маршрутизатора sun. Как мы описали в разделе "ICMP ошибки" главы 21, это всего лишь "мягкая" ошибка для принимающего TCP на хосте slip. Он фиксирует, что была принята ICMP ошибка, однако получение ошибки не разрывает соединение. Отправляются еще 9 проб "оставайся в живых", с интервалом в 75 секунд, перед тем, как хост прекращает свои попытки. Ошибка, возвращаемая приложению, генерирует другое сообщение: "No route to host" (нет маршрута к хосту). На рисунке 6.12 мы видели, что это соответствует ICMP ошибке о недоступности сети.
Как мы говорили ранее, характеристика "оставайся в живых" довольно спорная. Эксперты, работающие с протоколами, продолжают дебаты по поводу того, принадлежит ли она транспортному уровню или должна должна быть реализована непосредственно в приложении.
Она функционирует посредством отправки пробных пакетов по соединению, после того как соединение не использовалось в течение 2 часов. Могут возникнуть четыре различных сценария: удаленный конец функционирует и откликается, удаленный конец вышел из строя, удаленный конец вышел из строя и перезагрузился и удаленный конец в настоящее время недоступен. В нашем примере мы рассмотрели все эти сценарии и видели различные ошибки, возвращаемые для последних трех условий.
В двух первых примерах только эта характеристика позволила клиенту определить, что другой конец вышел из строя или вышел из строя и перезагрузился. В последнем примере, однако, с удаленным концом ничего не произошло, но соединение между ними было временно разорвано. Мы должны всегда помнить эти ограничения, когда используем опцию "оставайся в живых".
Упражнения