Multicast DNS и DNS-SD

26bc563cdb20a34df3374465fcbd56d4

Multicast DNS и DNS-SD являются ключевой составной частью стека Zero-configuration networking (zeroconf) и отвечают в нем за службу имен и автоматический поиск устройств в пределах локальной сети.

Технология zeroconf, позволяющая собрать работающую локальную сеть с нулевыми усилиями по администрированию и конфигурации, известна уже давно. Еще в 1980-х AppleTalk, локальная сеть для компьютеров и устройств Apple, позволяла просто соединить устройства проводами и сразу начать работать.

AppleTalk не выдержал конкуренцию с TCP/IP в эпоху Интернета, но идея сети, которая работает сама, не умерла. В мире TCP/IP Apple продвигает ее под именем Bonjour (ранее Rendezvous).

mDNS/DNS-SD являются одними из ключевых технологий в устройстве современного стека печати и сканирования. Кроме того, они могут использоваться в «облаке», позволяя отдельным компонентам облачного сервиса найти друг друга при условии, что они подключены к одной локальной сети, реальной или виртуальной.

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

Немного философии

Человек дает имена окружающим его предметам, чтобы получить возможность с ними взаимодействовать.

Если предметы — это устройства, подключенные к локальной сети, то типичными будут пожелания пользователя:

  1. Получить поименный список устройств определенной категории (например, принтеры, сканеры, сетевые хранилища)

  2. Выбрать нужное устройство из списка и начать с ним работать.

Имена должны быть понятными, чтобы пользователю легко было сопоставить имя с физическим устройством.

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

Multicasting

Классическая схема управления сетью предполагает использование какого-то выделенного контроллера, задачей которого является централизованное хранение конфигурации всей сети и распространение этой информации среди подключенных устройств.

В сетях zeroconf централизованный контроллер не используется. Конфигурационная информация хранится, в распределенном виде, на самих устройствах и зачастую совпадает с заводскими настройками.

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

В качестве транспорта используется UDP multicasting — способ отправки UDP-пакетов, в котором получателем является не конкретный хост, а группа машин.

В отличии от бродкастинга, при мультикастинге получателями пакетов являются не все подряд устройства в сети, а только те, которые явно изъявили желание получать пакеты из данной группы. Фильтрация обычно происходит на уровне контроллера сетевой карты. Это позволяет снизить нагрузку на CPU и уменьшить расход батарейки. в случае мобильных устройств.

При чем тут вообще DNS?

DNS, Domain Name System, это система, которая изначально создавалась для того, чтобы связать доменные имена с сетевыми адресами и адресами электронной почты. Ну, во всяком случае, в RFC1034 примерно это и написано.

Однако получилась в результате децентрализованная, распределенная key-value база данных вполне общего назначения.

База состоит из записей, которые в терминологии DNS называются Resource Records (RRs). Запись имеет имя, класс тип и значение. Ключом поиска всегда является сочетание имени и типа.

Класс в нашем случае он всегда IN (от слова Internet), поэтому на нем мы особо заострять внимание не будем.

Типов записей существует много, и разные типы используются для разных целей. Тип так же определяет, как должно интерпретироваться содержимое записи. Например, записи типа A содержат IPv4-е адреса, записи типа AAAA — IPv6-е и т.п.

Таким образом, чтобы узнать IP-адрес сайта www.example.com надо запросить запись с именем «www.example.com» и типом A. Полученный ответ и будет адресом сайта.

Итак, мы свели задачу получения адреса по имени к более общей задаче поиска по универсальной базе данных.

И имея под руками столь универсальную базу данных, как DNS, ее вполне можно приспособить для решения других задач, о чем мы дальше и поговорим.

Сам по себе DNS — весьма обширная тема, и если кому интересно, у меня есть про него отдельная статья

Классический (Unicast) DNS основан на иерархии серверов. Если кому-то потребуется получить информацию о домене www.example.com, то сначала он или она должны обратиться к т.наз. корневым серверам, чтобы узнать, кто занимается доменом com. Потом надо будет обратиться к серверу домена com, чтобы узнать, кто занимается доменом **example.com». Ну и т.д., до тех пор, пока конечный ответ не будет получен.

В случае Multicast DNS такой иерархии серверов не предусмотрено. Вопрошающий задает вопрос всех сети, и каждый отвечает сам за себя.

Имена

Общая структура имени

Поскольку у нас все же доменная система, то и имена, которые используются в качестве ключей для поиска, это не простые имена, а доменные.

Доменное имя состоит из последовательности меток (labels по-английски) , которые пишутся через точку и читаются справа налево. В имени www.example.com три метки: «www», «example» и «com».

Доменные имена образуют вложенную иерархию в том смысле, что example.com — это поддомен домена com, а www.example.com — домена example.com. По мере добавления меток слева пространство имен сужается, потому доменные имена и читаются в обратную сторону.

Вещи, которые не являются по природе своей доменами, все равно записываются в синтаксисе псевдодоменных имен. Например, IP-адрес 192.168.0.1 превратится в 1.0.168.192.in-addr.arpa. Специальный псевдодомен верхнего уровня, in-addr.arpa зарезервирован как раз для IPv4-х адресов, а адрес пишется задом наперед, потому что доменные имена читаются справа налево.

Для имен, управляемых через mDNS, зарезервирован домен верхнего уровня .local. Таким образом, типичное имя хоста в mDNS будет выглядеть, как hostname.local.

Кстати, точка, как разделитель — это способ внешнего представления имени. Внутри себя протокол DNS бинарный, и там просто последовательность строк.

Ограничения по размеру

Имена имеют ограничения: никакая метка не может превышать 63 байта, а всё имя не может быть длиннее 255-и байтов. Причем речь идет именно о байтах, а не о символах.

Набор символов

Классический DNS накладывает весьма жесткие ограничения на то, какие символы могут использоваться в метках. Для представления иноязычных слов используется весьма вычурная система кодирования под названием Punycode.

Однако сам по себе протокол никак не применяет с пользой для себя эти ограничения, это всего лишь договоренности. Которые, впрочем, могут использовать внутри себя конкретные реализации DNS.

Поэтому mDNS, не будучи обременен грузом совместимости со старыми реализациями, допускает использование внутри меток любых корректных последовательностей UTF-8.

И в этот момент следует вспомнить, что ограничения на размер выражены в байтах, а не в символах, и не всякий символ UTF-8 укладывается в байт.

Мультикастный DNS, как и классический, не чувствителен к регистру символов в имехах. Однако чтобы не таскать с собой таблицы UNICODE, авторы стандарта явно оговаривают, что эта нечувствительность распространяется только на символы ASCII.

Имена сервисов

Типы сервисов

DNS-SD оперирует понятием не устройства, а сервиса. Сервис — это штука, которая делает какую-то определенную полезную работу. Например, печатает, сканирует документы, хранит файлы, работает локальным веб-сервером.

Одно физическое устройство может содержать много сервисов. Например, печатать и сканировать.

DNS-SD связывает тип сервиса с сетевым протоколом, который сервис использует для своей работы. К примеру, принтер, работающий по протоколу IPP, будет использовать _ipp._tcp в качестве имени сервиса. Принтер со встроенным LPR-сервером будет анонсировать сервис _printer._tcp. Стандартный сервис для SMB-сервера — это _smb._tcp. Устройство, поддерживающее сразу несколько протоколов будет анонсировать отдельный сервис для каждого из протоколов.

Экземпляры сервисов (service instances)

Чтобы выбрать конкретное устройство из списка, нужно знать его имя. В терминологии DNS-SD это называется именем экземпляра сервиса (service instance name).

DNS-SD необычен тем, что имена экземпляров сервисов одновременно удобны для человека в том плане, что они выглядят по-человечески, и для компьютера, в том плане, что это не просто имя, а уникальный идентификатор устройства в пределах локальной сети.

Например, имя экземпляра принтера может выглядеть так: : «Kyocera ECOSYS M2040dn._ipp._tcp.local.», и это реальный пример (суффикс »_ipp._tcp.local.» пользователю не показыват).

Имена экземпляров не стоит путать с именами хостов (hostname). Имя хоста для того же принтера — KM7B6A91.local, не запоминающееся и довольно невзрачное. Тут дело в том, что пользователь будет иметь дело только с понятными человеку именами экземпляров, а имена хостов для устройств имеют сугубо техническое назначение и пользователю обычно не видны.

Уникальность имен

Имена экземпляров и хостов в пределах локальной сети уникальны. Объединенными усилиями устройства сами заботятся об этом. Прежде, чем устройство или компьютер заявят о своём присутствии в сети (в терминологии mDNS/DNS-SD это называется «опубликоваться», оно должно убедиться в том, что имя не занято кем-то другим.

Для этого перед тем, как начать использовать желаемое имя, устройство сначала опрашивает сеть в поисхах другого устройства, которое уже использует это имя, и только потом занимает его.

В этой процедуре есть много тонкостей и нюансов. В деталях она описана в RFC 6267, 8. Probing and Announcing on Startup.

Следует отметить, что не все имена по смыслу своему уникальные. Скажем, если имя обозначает «все принтеры в сети», то все принтеры его и публикуют, т.е. выдают свою часть совокупного ответа, но имя конкретного устройства публикует именно оно.

В принципе, протокол позволяет нескольким устройствам выступать, как одно целое. Т.е. публиковать одно имя на всех и как-то вместе за него отвечать. При этом протокол не оговаривает, как именно они должны координировать свои совместные усилия между собой.

Типы записей

Теперь, наверное, пришла пора немного погрузиться в технические детали, и для начала мы немного поговорим про основные типы DNS-записей.

DNS определяет много разных типов записей, но для наших целей основной интерес представляют следующие пять:

  • A — сетевой адрес, IPv4

  • AAAA — сетевой адрес, IPv6

  • PTR — указатель на другое доменное имя

  • SRV — описатель сервиса

  • TXT — текстовая запись, используется для передачи совокупности дополнительных параметров в формате «key=value…»

Записи типа A и AAAA

С этими записями всё достаточно просто. Они связывают доменное имя хоста с его IP-адресом.

Записи типа PTR

Записи типа PTR содержат в своем значении доменное имя. Т.е., имеем имя на входе (ключ поиска) и какое-то другое имя на выходе (значение записи). Такие записи обеспечивают связанность в базе DNS и используются в довольно разнообразных целях.

Простой пример, для получения доменного имени по IP адресу изпользуется PTR-запись, которая «транслирует» IP-адрес, представленный в форме домена, в доменное имя. Например:

102.1.168.192.in-addr.arpa. -> KM7B6A91.local.

Более сложный пример — получение по типу сервиса списка конкретных устройств, предоставляющих этот сервис. Например, получение списка всех принтеров. Но об этом чуть позже.

Записи типа SRV

Запись типа SRV содержит

Например, для использовавшегося уже в предыдущих примерах принтера она выглядит следующим образом:

0 0 631 KM7B6A91.local.
~ ~ ~~~  ~~~~~~~~~~~~~
| |  |         | 
| |  |         `--------- hostname, он нужен, чтобы получить IP-адрес
| |  `------------------- TCP (или UDP) порт
| `---------------------- "вес"
`------------------------ приоритет

Загадочные параметры «вес» и «приоритет» пришли, вместе с самой записью SRV, из классического DNS и описаны, вместе с прочими подробностями, в RFC2782. Там они предназначены для задания приоритета при выборе между многими однотипными сервисами (например, сервис отправки e-mail). В случае mDNS/DNS-SD выбор обычно происходит из одного варианта, и эти параметры не используются.

Назначение остальных двух параметров вполне очевидно.

Записи типа TXT

Эти записи позволяет в форме набора значений (key=value,…) предоставить всякую дополнительную информацию о сервисе. Конкретный набор параметров и их синтаксис зависит от сервиса.

Вот сильно сокращенный пример TXT-записи для упомянутого выше принтера:


pdl=image/pwg-raster,application/octet-stream,...   поддерживаемые форматы
ty=Kyocera ECOSYS M2040dn                           производитель+модель
Duplex=T                                            это дуплексный принтер
Color=F                                             не цветной
UUID=4509a320-00a0-008f-00b6-002507510eca           UUID устройства
. . .

Теперь всё это вместе

Как получить поименный список всех принтеров в сети, а потом параметры конкретного принтера? Разберем по шагам. И заодно немного поэкспериментируем.

Инструменты

Для экспериментов нам понадобится утилита, способная посылать произвольные mDNS запросы и печатать ответы. В готовом виде я такую утилиту не нашел и написал свою: mcdig.

Программка написана на Go, и чтобы ее запустить, ее потребуется собрать. К счастью, с Go это реально просто. Даже если Вы не знаете языка Go, инсталляция (после того, как у Вас есть компилятор) очень проста и сводится к одной команде:

$ go install github.com/alexpevzner/mcdig@master

Go сам загрузит все, что надо, сам соберет и выложит результат под именем ~go/bin/mcdig.

Кстати, под windows это тоже, в принципе, должно заработать, но я не проверял.

Ищем принтеры вручную

Для простоты мы ограничимся только принтерами, работающими по протоколу IPP. Имя типа сервиса для IPP-принтеров, это _ipp._tcp.

Шаг 1: ищем все экземпляры сервиса

Утилита mcdig посылает произвольные mDNS-запросы, некоторое время собирает результаты, а потом печатает их в формате «DNS zone file» — текстовых файлов, описывающих содержимое базы DNS.

Итак:

$ mcdig _ipp._tcp.local. ptr
;; QUESTION PSEUDOSECTION:
;_ipp._tcp.local.	IN	 PTR

;; ANSWER SECTION:
_ipp._tcp.local.	4500	IN	PTR	Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local.

;; AUTHORITY SECTION:
_ipp._tcp.local.	4500	IN	PTR	Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local.

;; ADDITIONAL SECTION:
Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local.       4500    IN      SRV     0 0 631 KM7B6A91.local.

Выдача mcdig напоминает по внешнему виду привычную системным администраторам выдачу утилиты dig, с той только разницей, что dig работает с обычным, классическим DNS-ом, и имеет немного побольше опций.

Секция QUESTION содержит, в формальном виде, вопрос, который мы спросили. В данном случае, мы запросили все записи типа PTR для имени _ipp._tcp.local, и на этот вопрос обязаны откликнуться все принтеры, поддерживаюшие протокол IPP.

Секция ANSWER содержит ответ или ответы (их может быть много).

Секция AUTHORITY содержит информацию о том, кто прислал ответ.

И, наконец, секция ADDITIONAL содержит информацию, которую мы не запрашивали, но которую, весьма вероятно, скоро запросим. Включение ее в ADDITIONAL секцию может ускорить процесс, сэкономив несколько оборотов запрос/ответ. Информация, которая туда попадает, на самом деле выбирается не случайно, по принципу, «а пришлю-ка я ему еще и это», а весьма тщательно регламентирована в RFC 6763, 12. DNS Additional Record Generation. Но сейчас мы не будем ее использовать, а вручную пройдем весь путь до конца.

Итак, мы запросили PTR-запись для имени .local и получили список имен экземпляров сервисов, которые реалузуют данный :

_ipp._tcp.local
~~~~~~~~~~~~~~~
 |
 `-> PTR -> "Kyocera ECOSYS M2040dn._ipp._tcp.local" (имя экземпляра сервиса)

Заметим так же, что ответ мы получили в домене _ipp._tcp.local, а не просто local — таким образом DNS-SD привязывает именя экзепляров к конкретному типу сервися.

Шаг 2. Получаем записи SRV и TXT

Итак, имя экземпляра сервиса мы уже нашли. В моем случае это выбор из одного пункта, но если экспериментировать в большой офисной сети, в которой правилами безопасности не запрещен mDNS-траффик, выдача вполне может быть и на пару экранов текста.

Теперь мы можем получить записи SRV и TXT:

$ mcdig Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local. srv
;; ANSWER SECTION:
Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local.   120    IN    SRV    0 0 631 KM7B6A91.local.
$ mcdig Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local. txt
;; ANSWER SECTION:
Kyocera\ ECOSYS\ M2040dn._ipp._tcp.local.   4500   IN    TXT    "txtvers=1" "pdl=image/pwg-raster,..."

Я немного подсократил выдачу mcdig, чтобы не перегружать статью.

Итак, по имени экземпляра сервиса мы получаем запись SRV, содержащую информацию, необходимую для работы с данным сервисом, и TXT, содержащую дополнительные технические детали:

Kyocera ECOSYS M2040dn._ipp._tcp.local.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
 | `---> SRV (hostname, TCP port)
 `-----> TXT (дополнительная информация о сервисе)

Шаг 3. Получаем IP-адреса

$ mcdig KM7B6A91.local. a
;; ANSWER SECTION:
KM7B6A91.local. 120     IN      A       192.168.1.102
$ mcdig KM7B6A91.local. aaaa
;; ANSWER SECTION:
KM7B6A91.local. 120     IN      AAAA    fe80::217:c8ff:fe7b:6a91

Итак, по hostname, полученном на предыдущем шаге, мы получаем IP-адреса устройства:

KM7B6A91.local
~~~~~~~~~~~~~~
 | |
 | `---> A    (адрес IPv4)
 `-----> AAAA (адрес IPv6)

Нюансы

Теперь поговорим о некоторых нюансах данной технологии

Множественные сетевые интерфейсы

Компьютер может быть подключен одновеменно к нескольким сетям. Например, WiFi и Ethernet. Или локальная сеть и офисный VPN.

Может получиться так, что эти сети не связаны между собой. В таком случае, их пространства имен, с точки зрения mDNS, между собой тоже не связаны. И может получиться, хотя и с малой вероятностью, что два разных устройства выберут одно и то же имя в разных сетях.

К сожалению, IP адрес тоже не всегда однозначно идентифицирует сеть. По-хорошему, прикладная программа, использующая mDNS/DNS-SD, должна учитывать, в какой из локальных сетей, доступных с данного компьютера, она нашла то или иное устройство, и предпринимать усилия, чтобы подключение к нему происходило именно в этой сети (например, явно указывая локальный для нее IP-адрес исходящего соединения, привязывая его к соответствующему сетевому интерфейсу).

Увы, всё это довольно сложно и не похоже та то, что пишут в учебниках по сетевому программированию, поэтому большинство программ так не делает.

IPv4 и IPv6

Казалось бы, тут нет особых сложностей. Все одинаковое, кроме типа записи, которая используется в самом конце, для получения IP адреса: A для IPv4, AAAA для IPv6.

В действительности, IPv4 и IPv6, работающие в одной и той же физической локальной сети, ведут себя, как две разных, не связанных между собой, логических сети. По-хорошему, любое устройство должно регистрировать одно и то же имя в обеих сетях и добиваться одновременного разрешения конфликтов по обеим сетям. Обычно они так и делают, но возможны нюансы.

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

Кроме того, следует учитывать, что ответы по разным протоколам приходят не одновременно. Если вы получили адрес IPv4, это не значит, что вы получили адрес IPv6. Он может прийти позже. А может и вовсе не прийти.

Надежность

Мультикасты могут теряться. Особенно это заметно в беспроводных сетяк, таких, как WiFi. Помогает кеширование ответов и постоянный мониторинг сети. Хорошо, когда в системе есть mDNS/DNS-SD демон (такой, как Avahi в Linux), который этим постоянно и занимается. Хуже, если протокол реализован прямо в процессе, который его использует (например, с помощью соответствующей библиотеки). У системного сервиса есть много времени после запуска системы, чтобы «прогреть» свой кеш. Прикладная программа должна «соображать» быстро, и приходится выбирать компромис между скоростью инициализации и надежностью.

Тем не менее, основываясь на опыте многолетней поддержки sane-airscan, автором которого я являюсь, могу сказать, что в общем и целом, mDNS/DNS-SD работает достаточно надежно для повседневного использования.

Безопасность

Увы, при использовании mDNS/DNS-SD нет гарантий достоверности полученного ответа. Это не является проблемой в домашней сети, и не является проблемой в сети, владелец которой полностью ее контролирует и следит за отсутствием в ней посторонних устройств (например, если mDNS используется в приватной локальной сети в «облаке» для поиска компонент облачного сервиса), но может стать проблемой в сети, в которой нельзя доверять всем участникам. Один из возможных векторов атаки: подсунуть фальшивый сервис и устроить MITM, перехватив траффик. Получить хотя бы выборочную копию документооборота своей организации может быть весьма интересно и познавательно :)

Большие корпоративные сети

В большой корпоративной сети в поле зрения может быть, например, 50 однотипных устройств. Из них пользователю могут быть интересны только две-три штуки, но остальные очень зашумляют ответ.

DNS-SD включает в себя средства несколько сузить диапазон поиска, но эти средства очень рудиментарны. Например, в настройках принтера обычно можно указать «location» (предполагается, что туда будет вписано что-нибудь вроде «Принтеры в бухгалтерии»), но более тонких настроек протоколом не предусмотрено.

Заключение

В данной статье я постарался дать некоторое понимание того, как устроена служба mDNS/DNS-SD. Я чувствую, что статья получается, вероятно, слишком насыщенная техническими подробностями. С другой стороны, многое осталось за кадром.

К сожалению, современные сети достаточно сложны для изучения и описания. Технологии, которые стремятся предоставить пользователю простую метафору для работы с компьютером, зачастую оказываются весьма сложными «под капотом». Это происходит потому, что их создатели берут на себя смелость и ответственность скрыть всю эту сложность, предлагая на поверхности интерфейс, понятный и удобный для человека.

© Habrahabr.ru