Транспортный уровень

Задачи транспортного уровня

Рассмотрим теперь четвертый уровень стека TCP/IP --- уровень протоколов TCP и UDP. Предыдущий уровень --- сетевой --- дал нам теоретическую возможность доставить пакет до любого получателя. На транспортом уровне решается вопрос реализации этой возможности, для чего необходимо обеспечить подтверждение получения данных получателем. Иными словами, мы должны решить следующий вопрос: доставлен ли IP-пакет?

Получение подтверждения о приеме переданных данных крайне важно, как для отслеживания качества взаимодействия по сети, так и по ряду других причин. Механизм подтверждения, казалось бы, прост: когда абонент получает данные, он отправляет подтверждение, а когда подтверждение получено, можно отправлять следующий фрагмент данных. Однако такой подход неэффективен, поэтому в протоколе TCP для ускорения работы используются одно подтверждение на несколько принятых пакетов и посылка некоторого объема данных без ожидания подтверждения предыдущих.

Кроме подтверждения доставки, транспортный протокол должен предоставлять следующие возможности. Если исходное сообщение большое, требуется разбить его на пакеты так, чтобы абонент затем мог восстановить исходные данные. На стороне абонента при этом должен быть механизм проверки, все ли пакеты пришли, и механизм сборки сообщения из пакетов. Возможна такая ситуация: данные были отправлены, но абонент ничего не получил. Пусть передаваемая информация разделена, скажем, на четыре пакета, которые отправляются последовательно, один за другим. Допустим, что абонент получает 1-й, 2-й и 4-й пакеты (3-й потерялся "по пути"). Нужно разработать механизм объединения этих четырех пакетов в поток так, чтобы получатель понял, что он не получил именно 3-й пакет (то есть тот 4-й, который он получил, есть именно 4-й, а не 3-й). В противном случае при "перемешивании" пакетов (например, в результате причуд маршрутизации вначале придет 4-й пакет, а только потом - 3-й) будет невозможно восстановить исходный порядок пакетов, а следовательно, и передаваемую информацию. Итак, необходимо решить вопрос манипулирования потоками данных. В случае, когда мы передаем сразу два потока данных, нужно сделать так, чтобы эти потоки друг от друга отличались.

Обеспечение надежной передачи должно начинаться с решения вопроса о подключении. Иными словами, перед тем как данные передавать, следует убедиться в том, что их кто-то будет принимать. К примеру, мы хотим отправить данные абоненту с адресом 158.250.10.1. Однако "существует" ли он для нас --- априори неизвестно. Даже в случае его существования мы не можем гарантировать, что маршрут, по которому пойдет пакет, функционирует корректно. Прежде чем начать передачу данных данному абоненту, следует обменяться с ним вспомогательной информацией. Если абонент не отвечает, то "добраться" до него мы не сможем, так что передавать данные бессмысленно.

Как видно даже из краткого описания возможностей протокола TCP, он передает заметное количество служебной информации. Однако всегда ли необходимо решение всех перечисленных задач? К примеру, если вся информация, которую надо передать, помещается в один пакет, то можно пойти более простым путем. Чтобы была возможность выбора, на транспортном уровне поддерживается два протокола: надежный протокол TCP и ненадежный протокол UDP. Пакеты протокола UDP обычно называют датаграммами.

Итак, можно выделить пять задач надежного транспортного протокола:

  • установка соединения;
  • подтверждение доставки;
  • контроль за целостностью данных;
  • манипуляция потоками данных;
  • отслеживание качества канала.

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

Как уже было сказано, помимо протокола TCP существует еще и протокол UDP. Следует разобраться, в каких ситуациях какой транспортный протокол предполагается использовать. Разберем подробнее указанный пример. Допустим, мы хотим послать ровно один пакет. Нужны ли нам в этом случае организация из него потока данных, отслеживание качества канала, или даже установка соединения? Если вся информация, которую мы собираемся передавать, умещается в один акт передачи данных, то ничего из перечисленного организовывать не нужно. В крайнем случае, повтор пересылаемых данных можно переложить на вышестоящий прикладной протокол. Поскольку проверка контрольной суммы осуществляется и при использовании протокола UDP, то единственная возможная проблема --- потеря пакета. Но в случае соединения по протоколу TCP дела обстоят не лучше: если первые пакеты при попытке установить соединение не дошли до абонента, то соединение просто не установится. Следовательно, в нашем случае разумно использовать UDP, а потом просто проверить: дошел ли наш пакет? К примеру, именно так устроен протокол DNS: на UDP-запрос должен прийти ответ, который, во-первых, подтверждает корректную доставку и, во-вторых, несет содержательную информацию.

Именно по этой причине на транспортном уровне всего два протокола передачи данных: один из них пять перечисленных свойств надежного протокола поддерживает --- это протокол TCP. Другой же не поддерживает ни одного из них, потому что все заключено в одной посылке данных, --- это протокол UDP.

Протокол TCP

Рассмотрим, как устроен трехуровневое подключение по протоколу TCP с точки зрения требований к надежному транспортному протоколу.

  1. Все начинается с установления подключения. Подключение по TCP двустороннее, то есть данные передаются в обе стороны. Клиент --- это тот, кто инициирует подключение ("программа, которая хочет"), а сервер --- тот, кто на него отвечает ("программа, которая может"). Итак, инициатор --- клиент --- подключается к серверу. Данные в дальнейшем передаются как от клиента к серверу, так и от сервера к клиенту.
  2. TCP устроен по принципу подтверждения: на каждый TCP-пакет (а он может быть гораздо больше, чем IP-пакет!), после того как он принят сервером, генерируется подтверждение, если все принято (все хорошо), или сообщение об ошибке, в случае если пакет "побился" по дороге. Сообщение об ошибке отправляется также в том случае, когда приходит что-то из того же потока данных, но не соответствующее ожиданиям. Это возможно, к примеру, когда в некоторый момент происходит timeout и некоторого пакета (или группы пакетов) внутри потока данных не приходит вообще: "Ты что мне шлешь 12-й? Я хочу 3-й!" Процесс это симметричный, подобно игре в волейбол. Данные, связанные с управлением, и собственно передаваемые данные можно объединять. Разумеется, может прийти и просто подтверждение, если посылать ничего не требуется.
  3. В каждом пакете передается также так называемая контрольная сумма, которая позволяет осуществлять контроль за целостностью данных. Все TCP-пакеты перенумерованы: в каждом соединении есть два счетчика SEQN (sequence number) --- по одному на каждое направление передачи. Счетчик инициализируется произвольно взятым числом при подключении и в дальнейшем увеличивается на объем передаваемых данных при каждой отсылке пакета. Такая схема позволяет, с одной стороны, определять последовательность пакетов и, с другой стороны, выяснять, что пропало и не задублировался ли пакет.
  4. Для того чтобы выполнить задачу разделения потоков данных, пары "адрес отправителя - адрес получателя" недостаточно. На транспортном уровне вводится новое понятие --- порт. Оно участвует в TCP и UDP и, кроме того, оказывается полезным на уровне интерпретации данных. Придумано оно по аналогии с портами ввода-вывода обычного компьютера. Когда происходит установление соединения, клиент подключается не просто к IP-адресу сервера, но к паре "IP сервера + некоторый порт", причем клиенту так же присваивается некоторый номер порта отправителя.
  5. Что касается отслеживания качества канала, то в TCP используется довольно хитрая технология. Не вдаваясь в ее описание, заметим, что главная используемая идея такова: вначале обмен идет маленькими пакетами, а далее этот обмен происходит чем успешнее, тем быстрее: чем больше данных готова принять принимающая сторона, тем больше данных отправляет отправитель.

Применение утилиты tcpdump

Откроем окно эмулятора терминала, перейдем в режим суперпользователя и запустим программу tcpdump со следующими параметрами:

# tcpdump -n  host 89.188.104.91
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes

Она не завершит свою работу, а будет отслеживать наши соединения с машиной, имеющей указанный IP-адрес. Откроем другое окно с терминалом и попробуем подключиться к ней:

$ netcat 89.188.104.91 80
(UNKNOWN) [89.188.104.91] 80 (www) : Connection refused

Как видно, в подключении нам отказали, а tcpdump тем временем выведет примерно следующий текст:

17:07:02.427692 IP 192.168.1.7.50898 > 89.188.104.91.80: S 2822348305:2822348305(0) win 5840 <mss 1460,sackOK,timestamp 4478628 0,nop,wscale 7>
17:07:02.439917 IP 89.188.104.91.80 > 192.168.1.7.50898: R 0:0(0) ack 2822348306 win 0

Попробуем иначе:

$ netcat 89.188.104.91 22
SSH-2.0-OpenSSH_4.3p2 Debian-9

На этот раз все получилось лучше: мы видим приглашение для входа сервиса SSH. Не будем продолжать "разговор" с 89.188.104.91 и завершим соединение, нажав Ctrl+C. Что же за это время выведет tcpdump?

17:08:30.366846 IP 192.168.1.7.42542 > 89.188.104.91.22: S 4202224582:4202224582(0) win 5840 <mss 1460,sackOK,timestamp 4500612 0,nop,wscale 7>
17:08:30.378474 IP 89.188.104.91.22 > 192.168.1.7.42542: S 2533826825:2533826825(0) ack 4202224583 win 5792 <mss 1360,sackOK,timestamp 353673920 4500612,nop,wscale 6>
17:08:30.378533 IP 192.168.1.7.42542 > 89.188.104.91.22: . ack 1 win 46 <nop,nop,timestamp 4500615 353673920>
17:08:30.394466 IP 89.188.104.91.22 > 192.168.1.7.42542: P 1:32(31) ack 1 win 91 <nop,nop,timestamp 353673924 4500615>
17:08:30.394528 IP 192.168.1.7.42542 > 89.188.104.91.22: . ack 32 win 46 <nop,nop,timestamp 4500619 353673924

Что это означает? Вначале мы подключились по IP-адресу 89.188.104.91 на 80-й порт, а потом по тому же адресу, но на 22-й порт. Во втором случае, как видно, нас ожидал OpenSSH-сервер на Debian.

Поскольку установление подключения двустороннее, то когда происходит ответное установление подключение от сервера к клиенту, оно происходит также по определенному порту. Его номер сообщается клиентом в самом начале соединения. В данном случае порт получателя имеет номер 22, а клиент передает номер порта отправителя --- 42542. Как видно, эти номера портов сохраняются и в дальнейшем. Если теперь мы будем устанавливать следующее подключение к тому же самому серверу, в качестве порта отправителя будет использовано другое число. Именно это число и будет отличать соответствующие потоки данных: IP-адрес получателя, порт получателя и IP-адрес отправителя не поменялись, а вот порт отправителя у них разный. Иными словами, каждое TCP-соединение использует свой собственный порт для идентификации отправителя.

Распределение портов транспортного уровня

Для использования портов TCP и UDP отправитель и получатель должны иметь некоторые одинаковые предположения, какая сетевая прикладного уровня служба с каким портом связана. Согласно некоторой договоренности, разные типы приложений традиционно принимают соединения на разных портах. Поэтому данные, приходящие на разные порты, естественно интерпретировать по-разному. При подключении по 80 порту то, что мы передаем, будет интерпретироваться как HTTP-запросы, а по порту 22 нас ждет Secure Shell (SSH). Несложно понять, зачем это соглашение понадобилось. Дело в том, что никакого другого способа указать клиенту, по какому порту подключаться, не существует (кроме, разумеется, словесного описания: "У меня есть сервер, подключайся, пожалуйста, по порту 9090").

Существует организация IANA, в которой можно зарегистрировать свое приложение, сказав: пусть теперь теперь такой-то порт исключительно вот для этого используется. Список зарегистрированных портов можно посмотреть в файле /etc/services:

$ head -35 /etc/services
# /etc/services:
#
# Network services, Internet style
#
# The latest IANA port assignments can be gotten from
#       http://www.iana.org/assignments/port-numbers
# (last updated 8 November 2004)
#
# The port numbers are divided into three ranges: the Well Known Ports,
# the Registered Ports, and the Dynamic and/or Private Ports.
#
# The Well Known Ports are those from 0 through 1023.
# The Registered Ports are those from 1024 through 49151.
# The Dynamic and/or Private Ports are those from 49152 through 65535.
#
# Note that it is presently the policy of IANA to assign a single well-known
# port number for both TCP and UDP; hence, most entries here have two entries
# even if the protocol doesn't support UDP operations.
#
# Not all ports are included, only the more common ones.
#
# Each line describes one service, and is of the form:
#
# service-name  port/protocol  [aliases ...]   [# comment]

tcpmux          1/tcp                           # TCP port service multiplexer
tcpmux          1/udp                           # TCP port service multiplexer
rje             5/tcp                           # Remote Job Entry
rje             5/udp                           # Remote Job Entry
echo            7/tcp                           # Echo
echo            7/udp                           # Echo
discard         9/tcp           sink null       # Discard
discard         9/udp           sink null       # Discard
systat          11/tcp          users           # Active Users
systat          11/udp          users           # Active Users
$ grep ^ssh /etc/services
ssh             22/tcp                          # SSH Remote Login Protocol
ssh             22/udp                          # SSH Remote Login Protocol

Можно заметить, что временный порт для клиента всякий раз выбирается достаточно большим, согласно рекомендациям из /etc/services он должен выбираться, начиная с 49152, но часто --- начиная с 32000.

Протокол UDP

Скажем несколько слов и о протоколе UDP. На самом деле в TCP-пакетах и UDP-датаграммах нет почти ничего общего, кроме IP-адресов и портов получателя и отправителя. Если на прикладном уровне не организовано специальной поддержки подтверждений, UDP-датаграмма может уйти "в никуда". И, разумеется, есть класс задач, где это единственно возможная организация передачи данных. Классический пример --- широковещание. Никому не придет в голову от всех клиентов, которые смотрят потоковое видео, получать подтверждения и сообщения об ошибках и обрабатывать их. Другая область применения --- случай, когда сам факт обмена данных заключается в посылке очень маленьких пакетов, а работа на прикладном уровне подразумевает, что ответ будет отправлен. Типичный пример --- служба доменных имен DNS. Если мы ждем ответа от DNS-сервера, то его можно ждать и на прикладном уровне - с таким же успехом, как и на уровне установления TCP-соединения. Незачем из одного пакета делать четыре - разумнее экономить трафик, причем чем выше уровень DNS-сервера, тем это выгодней.

Есть и еще одно соображение, которое стоит рассмотреть. Предположим, у нас есть очень медленный (по времени отклика) канал. Оказывается, по такому каналу удобнее "гонять" UDP. Это хорошо видно из следующей схемы:

../networks_tcp_vs_udp.png

Но в реальном использовании необходимо оценивать и вероятность возникновения ошибки: если мы узнаем о ней слишком поздно, то будет послано много лишних пакетов. В современном мире среды передачи данных и компьютеры работают все быстрее, а уровень надежности принципиально не повышается. Поэтому и в таких случаях становится все выгоднее использовать протокол с установлением соединения. Классический пример --- сетевая файловая система NFS, переходящая c UDP на TCP в качестве основного протокола.


Сведения о ресурсах

Продолжительность (ак. ч.)

Подготовка (календ. ч.)

Полный текст (раб. д.)

Предварительные знания

Level

1

1

1

1