Протокол UDP: простой транспорт без установления соединения, сравнение с TCP и типичные сценарии применения.
Каков размер заголовка UDP?
Почему VoIP и онлайн-игры предпочитают UDP, а не TCP?
Какой UDP-порт используется для DNS?
Как DNS обеспечивает надёжность, используя UDP?
Упорядочивает ли UDP датаграммы при доставке?
Продолжаем с вами разговор по курсу ICND1. И на очереди у нас то, что может бегать поверх сетевых протоколов. У нас есть протокол IPv4, есть протокол IPv6. Мы с ними разобрались рамочно, как они работают. И теперь пора поговорить, что бегает поверх них. Чаще всего в современном мире мы будем видеть два основных протокола, которые вкладываются в IP — это TCP и UDP. Помимо того, что мы там ICMP ещё видели, того, что может таскать полезные данные, это TCP и UDP. Начнём с первого, с UDP, потому что он попроще. И когда мы будем проходить его, у нас будет меньше штук, на которые можно будет отвлекаться. А потом, когда в следующем занятии мы будем смотреть TCP, мы уже будем смотреть на то, как он работает, с отсылкой на UDP. UDP — очень простой протокол. Фактически это протокол, который позволяет передавать данные с минимумом дополнительных функций.
Если вам хочется просто передать какие-то данные в IP-пакете, вы берёте и вкладываете эти данные, но в качестве прокладки между данными и IP вкладываете маленький UDP-шный заголовочек. Функции, которые несёт в себе UDP — это минимальный набор, который вообще требуется от протокола транспортного уровня. Это мультиплексирование различных потоков данных, которые идут от отправителей к получателю. Помните, что у нас на четвёртом уровне модели OSI есть такая задача, как мультиплексирование различных потоков. Вот эту задачу UDP и реализует. Если мы говорим про отправитель — у нас есть отправитель, дальше получатель, и между ними есть какие-то роутеры, которые между собой связаны. У меня криво получились эти роутеры, но тем не менее. Роутеры между собой связаны. Мы с помощью IP можем передавать IP-пакеты, которые пройдут через всю сеть и доставятся от отправителя до получателя.
Причём разные IP-пакеты, возможно, пойдут разными трассами, но тем не менее они как-то худо-бедно, наверное, в итоге дойдут. Если мы хотим поверх IP что-то передавать, то мы можем в IP напрямую данные вложить, но тогда будет проблема, если мы запустим два разных приложения — одно приложение и другое приложение на отправителе и, соответственно, на получателе. Как сделать так, чтобы трафик первого приложения не пересёкся с трафиком второго приложения? Они же будут достаточно монолитно бежать, и невозможно будет понять по IP-пакетам, к какому приложению они относятся. UDP как раз решает такую задачу, причём решает эту задачу очень простым образом. Каждое приложение будет пронумеровано на отправителе и на получателе. Например, у нас левое приложение — допустим, TELNET-клиент — оно у нас будет пронумеровано 1, 2, 3, 4. А правое приложение — допустим, HTTP-клиент — оно будет пронумеровано 5, 6, 7, 8. Каждое приложение получает свой собственный номер.
И дальше два приложения на получателе тоже запускаются, и каждое из этих приложений тоже получает свой номер. Здесь у нас будет приложение 2, 3, 4, 5 и приложение 4, 5, 6, 7. К примеру, это просто какие-то условно случайные номера. И когда мы отправляем какой-то кусочек данных с помощью протокола UDP, мы указываем, какое приложение с каким номером какому приложению с каким номером отправило данные от отправителя до получателя. Каждый конкретный экземпляр приложения будет иметь свой собственный номер, когда он запущен. И мы посылаем данные, пишем от кого кому — какой номер посылает, какому номеру получателя. И больше ничего UDP особо не делает. Единственное, что там есть механизм, который позволяет отследить, данные побились по дороге или нет. Можно считать это тоже функцией UDP. Но по сути UDP только мультиплексирует потоки и больше ничего.
UDP не отслеживает порядок отправляемых данных. Он может меняться. Может быть такое, что вы отправите сначала один кусочек данных, потом другой кусочек данных, и они придут в обратном порядке. UDP не контролирует доставку. Вы что-то отправили — оно может дойдёт, а может и нет. Никаким боком UDP не решает никакие дополнительные задачи. Он просто вкладывает данные в IP, а IP эти задачи тоже никак не решает и имеет полное право поменять порядок следования пакетов. Нет контроля перегрузки. Если вдруг вы отправляете много-много данных, и эти данные все встанут в очередь в какой-то буфер в интерфейсе, и этот буфер заполнится, то никоим боком вы не можете в UDP понять, что это произошло, и начать посылать данные помедленнее. UDP просто для этого не предназначен. У него нет никаких служебных задач, которые он решал бы, кроме мультиплексирования потоков данных. И немножко контроль целостности — если вдруг данные побились по дороге, это можно отследить.
UDP за счёт того, что не решает никакие дополнительные задачи, делает ту единственную задачу, которую он всё-таки может решать, очень хорошо и очень быстро. Там, где вам нужно будет минимизировать время на отправку и обработку данных, там UDP будет очень полезен. Это все приложения, которые требуют минимальных задержек при передаче данных. Голос, видео, потоковое видео, подчеркну. Всякие онлайн-игры. Это всё делается в UDP, потому что UDP позволяет обработать данные за минимальное время. Он никакие дополнительные расходы по времени не привносит. И, как следствие, он очень удобен именно в тех случаях, где требуется минимальная задержка. Формат у UDP-шной датаграммы будет крайне простой. Вот тут идёт заголовок IP, здесь идут данные, а вот между ними буквально 8 байт заголовка. Номер приложения, откуда отправляем данные, номер приложения, куда отправляем данные, UDP length — это суммарная длина заголовка плюс полезных данных.
И чек-сумма. Всё, больше ничего нет. Причём чек-сумма считается по-хитрому — она считается не от самих данных и не от заголовка UDP, она считается от так называемого псевдозаголовка, который включает в себя UDP-шные данные, UDP-шный заголовок и кусочек заголовка IP. Кусочек, потому что там считаются IP-адреса отправителя и получателя и что внутри лежит — поле протокол. Таким образом, учитывая, что поле протокол и IP-адреса не меняются по дороге, чек-сумма тоже не меняется по дороге. Сделано это для того, чтобы если вдруг у вас каким-то образом получится так, что данные пришли на уровне IP на получателя, который на самом деле их не должен был получать, чтобы в UDP у вас побилась чек-сумма тоже. Чтобы на уровне UDP вы не обрабатывали данные, которые изначально не ваши. Как видите, заголовок очень простой и решает он ровно одну задачу — понять, кто кому отправил данные.
Когда мы говорим про то, что каждое приложение будет пронумеровано, мы говорим про то, что эти номера приложений будут называться специальным словом — порт. Вообще говоря, порт не обозначает ничего конкретного. Протокол, который был предшественником UDP — там, если вдруг вам будет интересно, был такой протокол, у которого в заголовке было ровно одно поле. Не кто кому отправил данные, а просто что там внутри лежит. И, соответственно, там было всего одно единственное указание на тип данных. Там это имело смысл — вы указывали порт, и в этом порту вы указывали, что внутри лежит, к примеру, телнет. И когда конечный получатель получал такие данные, он сразу понимал — это надо отправить на обработчик телнета. Или вы отправляли данные, внутри указывали — это NTP, Network Time Protocol.
И, опять же, у вас данные отправлялись на конкретный обработчик NTP. Но для того, чтобы это работало, нужно было, чтобы все эти порты были хорошо известны. У того протокола, который был предшественником UDP — я, к сожалению, запамятовал его название. Если вдруг вам будет интересно, я потом могу рассказать. Но смысл там был именно такой, что все порты того старого протокола-предшественника были хорошо известны, вы просто их наперёд все знали. Когда появился UDP, он появился как развитие того древнего протокола, хотя сам UDP очень-очень старый, но тот протокол был совсем древним. Когда появился UDP, у вас стало в каждом кусочке данных, которые вы отправляете по UDP, два поля — кто и кому отправляет данные. И вообще, по-хорошему, это словом «порт» уже называть не совсем корректно, потому что это просто фактически номер приложения. У вас на компьютере отправителя все приложения пронумерованы, и на компьютере получателя все приложения пронумерованы. И эти номера могут быть случайными.
Вам никто не мешает отправить датаграмму с номера 10117 на номер 20119. Два абсолютно случайных номера приложения могут друг другу без особых проблем передавать данные. И чаще всего, кстати, когда мы говорим, допустим, про IP-телефонию, ровно так и происходит. Мы запускаем приложение IP-телефон — процесс IP-телефонии. Они придумывают себе совершенно случайные номера, именно UDP-использующие приложения. И они между собой общаются. И так это и выглядит. Причём заранее знать, какой именно номер будет использован у приложения IP-телефон на каждой стороне, нельзя. Но в большинстве ситуаций, когда мы будем использовать UDP с какими-то наиболее популярными приложениями, хотя бы один из номеров вам будет известен.
Фишка в том, что если у вас есть отправитель и получатель — два компьютера — один из этих компьютеров будет инициатором взаимодействия, а второй будет согласен это взаимодействие вести. И мы в этом случае будем называть их специальными словами. Клиент — это тот, кто хочет инициировать подключение прямо в данный момент. И сервер. Сервер — это тот, кто согласен установить подключение с клиентом. Никакой специальной сакральной логики за этими обозначениями нету, кроме обозначения поведения этих узлов в данный конкретный момент времени. Когда у кого-то просыпается желание установить соединение, он становится клиентом. А когда кто-то согласен устанавливать какие-то соединения с соседями, он будет называться сервером. Поведение у них отличается ровно в тот момент, когда у вас возникает желание начать какие-то данные передавать. После того, как это желание возникло, дальше уже всё идёт абсолютно одинаково.
Что клиент, что сервер просто отправляют друг другу данные. UDP не требует никакой специальной процедуры согласования соединения или чего-то ещё. В тот момент, когда ваше приложение хочет какие-то данные внезапно отправить, и при этом оно только захотело это сделать в самую первую секунду — оно говорит: я бы хотел начать новую передачу данных. Термина «соединение» в UDP нет, но начать процедуру передачи данных — вот так более правильно. И в этот момент приложение говорит: я бы хотел стать клиентом, и начинает отправлять данные. Никакой специальной процедуры, которую надо было бы выполнить по определённому алгоритму, нет. Вы просто берёте и отправляете. И вы пишете свой номер в качестве того, от какого приложения вы эти данные отправляете. Допустим, 20119 у нас будет номер своего приложения.
И вам надо каким-то образом указать, кому вы эти данные отправляете. Поскольку процедуры согласования подключения или какой-то предварительной установки сессии нету, то в большинстве ситуаций, когда мы хотим данные какие-то отправлять и у нас ещё нет никакого установленного соединения, нам надо что-то здесь написать. Надо какой-то порт указать, номер приложения на получателе. И в этой ситуации нас спасают так называемые хорошо известные порты — well-known порты. Хорошо известные номера приложений, которые с вероятностью единицы будут на том компьютере, куда мы хотим отправить данные. Например, если вы используете так называемый сервер DNS. Что такое сервер DNS? Это некий компьютер, который знает, какой IP-адрес какой текстовой метке будет соответствовать, и вы можете к нему обратиться и сказать: дорогой узел, скажи мне, пожалуйста, если я хочу зарезолвить имя www.networkeducation.ru в IP-адрес, какой именно IP-адрес там будет. Вот это и есть DNS-сервер.
Для того, чтобы обращаться к хорошо известным приложениям, есть как раз эти самые хорошо известные номера портов. Тот процесс, который будет отвечать на запросы пользователей — какой IP-адрес у такого-то имени, скажи его — он будет использовать, например, 53-й порт. Вы отправляете как DNS-клиент запрос со своего номера, который не хорошо известный, который вы только что придумали в момент запуска этого самого DNS-клиента — 20119, а в качестве порта назначения вы указываете порт 53, хорошо известный порт DNS-сервера. После того, как вы такую датаграмму отправляете, сервер получает — сервер в кавычках, здесь нет такого специального особенного поведения у сервера — сервер просто получает какие-то данные. Он получает какой-то IP-пакет, внутри лежит UDP-шный заголовок, в UDP-шном заголовке написано, кому это адресовано — 53-й порт.
Приложение DNS-сервера на компьютере — вот этом, в кавычках, сервере — оно получает запрос от пользователя, дальше каким-то образом этот запрос обрабатывает, берёт в базе данных или в каком-то списке смотрит результат и дальше отправляет ответное сообщение. Ответное сообщение идёт с 53-го порта, с приложения номер 53 на приложение номер 20119. Таким образом получается, что если у вас есть какое-то приложение, которое согласно слушать пользователей, которое согласно отвечать на вопросы пользователя, Если хотите, оно будет в кавычках сервером. А то приложение, которое будет задавать вопросы, оно будет клиентом. При этом после того, как клиент задал вопрос, сервер знает уже и IP-адрес клиента, и номер приложения, номер порта на нём, который используется. Дальше взаимодействие уже будет вестись в рамках логики конкретного приложения,
и приложение DNS-сервер будет отправлять сообщение приложению DNS-клиент точно такие же сообщения, точно так же всё будет происходить, как и в случае, если DNS-клиент отправляет данные DNS-серверу. После того как первое взаимодействие проведено, когда в кавычках сервер узнал адрес в кавычках клиента, уже никакой разницы между ними нет. Никто из них не будет ведущим, никто из них не будет ведомым, никто из них не может стать более главным, чем другой. Некоторые порты вы должны будете знать. Нужно будет знать 53-й порт DNS. Неплохо будет, если вы запомните номера 67 и 68. Эти номера портов используются в протоколе BootP. Он же сегодня известен как DHCP. Вы помните, да? DHCP — это надстройка над протоколом BootP. Соответственно, 67-й порт — это порт,
соответствующий серверу, который раздаёт IP-адреса. И 68-й порт — это порт клиента. Если у нас клиент просыпается, он отправляет сообщение с 68-го на 67-й порт. Если у нас просыпается сервер, который хочет ответить клиенту на его discover или request, он отправляет сообщение с 67-го на 68-й порт. Здесь оба порта хорошо известны, и это на самом деле довольно большая редкость. Очень мало протоколов используют хорошо известный и порт источника, и порт назначения. В подавляющем большинстве ситуаций, когда вы будете использовать DHCP, когда вы будете использовать UDP, у вас один порт будет хорошо известный, и он будет соответствовать серверу. А другой порт, клиентский, как правило, будет неизвестен, он заранее не фиксируется никаким образом. И в тот момент, когда вы запускаете приложение, только в тот момент у вас номер этого порта
выделяется для конкретного процесса. Фишка в том, что если вдруг вы захотите, вы можете запустить, допустим, два DNS-клиента, и вы можете на одном и том же сервере резолвить два разных имени. Вы можете на DNS-сервер, если вы используете 53-й порт назначения и разные порты источника, отправить, допустим, запрос из-под DNS-клиента 1001 на 53-й порт с вопросом www.network-education.ru, а с порта, допустим, 1002, тоже на 53-й порт — www.network-education.ru. И таким образом ответы придут на разные номера портов, на разные номера приложений, и в этом случае каждый отдельный запрос DNS, каждый резолвинг, не перепутается с соседним. Если вы будете использовать один порт, хорошо известный, а порт источника на клиенте будете выбирать динамически, то в этом случае вы сможете запустить сколько угодно параллельных сессий
в рамках одного и того же какого-то большого приложения. В случае с DHCP это лишено смысла, потому что вы не можете запросить у сервера два разных адреса, например. У вас клиент, который хочет получить адрес, он всего один на интерфейсе, и непонятно, какое взаимодействие могло бы пригодиться, если бы вдруг у вас была возможность запустить клиенты на получение адреса с разными номерами портов. Но опять же, здесь 68-й порт у клиента — это не столько требование здравого смысла или логики, сколько тяжёлое наследие древних времён, когда протокол BootP в свою очередь развивался из ещё более древнего протокола, у которого как раз были хорошо известны номера портов, тот самый один номер порта, который указывался вообще всего один.
69-й UDP-шный порт — это TFTP, Trivial File Transfer Protocol. Протокол, который упаковывается в UDP и позволяет рамочно, но передавать файлы. Это не протокол FTP. FTP работает по TCP, по 20-му и 21-му порту. А это UDP-шный протокол, который очень простой в реализации, который позволяет только передавать файлы и практически больше ничего. Он не позволяет делать листинг директории. Он не позволяет, допустим, файлы разных типов передавать — двоичные или текстовые — с разной степенью успешности. Он просто умеет передавать файлы, и всё. Он при этом обеспечивает какой-то базовый, простенький контроль того, что передаётся, но по функциям он, конечно, очень минималистичный. 123-й порт — NTP. Причём 123-й порт у NTP —
это и порт источника, и порт назначения. Опять же, это наследие древних времён, когда было просто указание, что внутри лежит NTP, без указания, кто кому что посылал. Поэтому в реальности все NTP-шные пакеты, которые вы будете видеть, они идут со 123-го порта на 123-й порт. И NTP-клиент, и NTP-сервер имеют одинаковый, хорошо известный номер UDP-порта. Как следствие, это будет приводить к проблемам с, допустим, NAT-ом. Но про NAT мы будем отдельно говорить. 161-й — это SNMP, причём SNMP-агент. Ещё есть 162-й — тоже SNMP, но SNMP-менеджер. И там тоже будет со 161-го на 162-й идти или наоборот. Где UDP имеет смысл использовать? Там, где не требуются какие-то дополнительные функции или где требуется минимальное время
обработки пакета. У нас есть протокол UDP, у нас есть протокол TCP. TCP делает много разных других задач, помимо того, что мультиплексирует потоки данных. Среди основных функций протокола TCP — это функция контроля доставки. Что-то отправляя по TCP, вы можете быть уверены, что либо это дойдёт до получателя, либо вы узнаете о том, что до получателя это не дошло, и сможете прозрачно, опять же, для приложений повторить доставку. В случае с UDP никакие дополнительные задачи не решаются. Как следствие, время на их решение не тратится. И там, где вам требуется минимальное время обработки данных, минимальное время пересылки данных, там UDP и будет полезен. Кроме того, UDP достаточно неплохо себя чувствует в средах с маленькими MTU, поэтому если вы используете UDP-шные вложения, то UDP вам ещё и полосу экономит.
Если у вас максимальный размер пакета, который вы можете отправить, небольшой, то все заголовки, которые вы будете отправлять, они будут отправляться каждый раз при отправке каждого пакета. Если у вас пакеты мелкие, а заголовки в них большие, то вы больше процентов полосы пропускания тратите на передачу заголовков, меньше на полезные данные остаётся. Но у UDP заголовок крошечный, поэтому он небольшую экономию обеспечивает по сравнению, например, с тем же TCP. Из классических приложений, которые UDP используют, — это телефония Voice over IP, это потоковое видео, но по сути своей та же телефония, только с видеокартинкой. Просто в телефонии упаковывают кусочки звука в мелкие пакеты, которых много бежит, и они все должны пробежать по сети за одинаковое время. А если мы говорим про всякие потоковые видео, системы телепрезенс и прочее, то там ещё кроме звука и видео упаковывается тоже в пакетики, которые тоже должны пробежать по сети
тоже за одинаковое время. Онлайн-игры, которые опять же связаны с реальным временем. Во времена моей юности была популярная игра про террористов и контртеррористов, которые друг в друга стреляли. Вы выглядываете из угла, видите — террорист бежит, вы в него стреляете, и естественно, что время, которое должно пройти с того момента, как вы нажали кнопку мыши, до времени, когда и вы, и ваш оппонент поймут, что вы его застрелили, оно должно быть минимальным, потому что если вы нажмёте кнопку сейчас, а машина поймёт, что вы в него попали только через 2 секунды, естественно, это будет очень сильно напрягать и раздражать. Поэтому вы нажали кнопку и сразу понимаете — попали или не попали. UDP прекрасно эту возможность обеспечивает именно за счёт того, что он ничего не умеет делать толком, он отправляет данные, и дальше уже конечное приложение будет разбираться. Попал, не попал. А оно получает возможность
это сделать очень быстро, благодаря тому, что UDP ни на что больше времени не тратит. Такой вот очень простой протокол. Простой, потому что у него очень простой заголовок, который решает очень простую, фактически, одну задачу. Про контрольную сумму уже сказал, что она позволяет определить, что данные какие-то побились. Если вдруг вы принимаете датаграмму, видите, что в ней контрольная сумма не сходится, вы её просто выкидываете и больше ничего не делаете. UDP не умеет переотправлять данные самостоятельно. Если вдруг вы обнаружили, что контрольная сумма у вас не сошлась, вы никоим образом не уведомляете отправителя, и отправитель в UDP тоже не знает о том, что данные какие-то до получателя не дошли, или дошли, но с повреждениями, и он их выкинул, и сам протокол UDP ничего не переотправляет. Если вдруг приложение захочет воспользоваться надёжной доставкой,
само приложение должно догадаться, что какие-то данные не доставились, и переотправить эти данные в отдельном UDP-шном сообщении. Сам протокол ничего этого не делает. Такой вот протокол, очень простой, простой как барабан буквально. И будет, наверное, интересно сравнить его со следующим протоколом, который называется TCP.