Структура DNS пакета

image
Решил как то написать снифер DNS, так сказать just for fun. Просто посмотреть какие адреса в моей системе резолвятся. Протокол старый, документации должно быть много. Много. Но все статьи очень не полные и заканчиваются, на самом интересном моменте. Да, есть rfc1035, но хотелось бы на русском и с пояснениями. Собственно по накоплению опыта и разбора пакета и созрела данная статья. Она рассчитана на тех, кто понимает, что такое DNS и понимает, что бывают запросы и ответы. Для тех, кто хочет немного разобраться в структуре данного протокола.

Статья предполагает теорию, а потом немного практики.

    +---------------------+
    |        Header       | Заголовок
    +---------------------+
    |       Question      | Секция запросов
    +---------------------+
    |        Answer       | Секция ответа
    +---------------------+
    |      Authority      | Секция ответа об уполномоченных серверах
    +---------------------+
    |      Additional     | Секция ответа дополнительных записей
    +---------------------+

Header  — Заголовок DNS пакета, состоящий из 12 октет.

Question section — в этой секции DNS-клиент передает запросы DNS-серверу сообщая о том, для какого имени необходимо разрешить (зарезолвить) запись DNS, а также какого типа (NS, A, TXT и т.д.). Сервер при ответе, копирует эту информацию и отдает клиенту обратно в этой же секции.

Answer section — сервер сообщает клиенту ответ или несколько ответов на запрос, в котором сообщает вышеуказанные данные.

Authoritative Section — содержит сведения о том, с помощью каких авторитетных серверов было получена информация включенная в секцию DNS-ответа.

Additional Record Section — дополнительные записи, которые относятся к запросу, но не являются строго ответами на вопрос.

Записей в секциях может быть как несколько, так и не быть вообще. Всё определяется заголовком.

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      ID                       |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    QDCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ANCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    NSCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                    ARCOUNT                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

ID (16 бит) — данное поле используется как уникальный идентификатор транзакции. Указывает на то, что пакет принадлежит одной и той же сессии «запросов-ответов» и занимает 16 бит.
QR (1 бит) — данный бит служит для индентификации того, является ли пакет запросом (QR = 0) или ответом (QR = 1).
Opcode (4 бита) — с помощью данного кода клиент может указать тип запроса, где обычное значение

  • 0 — стандартный запрос,
  • 1 — инверсный запрос,
  • 2 — запрос статуса сервера.
  • 3–15 — зарезервированы на будущее.


AA (1 бит) — данное поле имеет смысл только в DNS-ответах от сервера и сообщает о том, является ли ответ авторитетным либо нет.
TC (1 бит) — данный флаг устанавливается в пакете ответе в том случае если сервер не смог поместить всю необходимую информацию в пакет из-за существующих ограничений.
RD (1 бит) — этот однобитовый флаг устанавливается в запросе и копируется в ответ. Если он флаг устанавливается в запросе — это значит, что клиент просит сервер не сообщать ему промежуточных ответов, а вернуть только IP-адрес.
RA (1 бит) — отправляется только в ответах, и сообщает о том, что сервер поддерживает рекурсию
Z (3 бита) — являются зарезервированными и всегда равны нулю.
RCODE (4 бита) — это поле служит для уведомления клиентов о том, успешно ли выполнен запрос или с ошибкой.

  • 0 — значит запрос прошел без ошибок;
  • 1 — ошибка связана с тем, что сервер не смог понять форму запроса;
  • 2 — эта ошибка с некорректной работой сервера имен;
  • 3 — имя, которое разрешает клиент не существует в данном домене;
  • 4 — сервер не может выполнить запрос данного типа;
  • 5 — этот код означает, что сервер не может удовлетворить запроса клиента в силу административных ограничений безопасности.

QDCOUNT(16 бит) — количество записей в секции запросов
ANCOUNT(16 бит) — количество записей в секции ответы
NSCOUNT(16 бит) — количество записей в Authority Section
ARCOUNT(16 бит) — количество записей в Additional Record Section

                                 1  1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                     QNAME                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QTYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     QCLASS                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

QNAME — Каждая запись запроса и ответа начинается с NAME. Это доменное имя, к которому привязана или которому «принадлежит» данная запись. Она закодирована как серия меток. На этом моменте следует остановиться несколько поподробнее.
В статьях, что я видел, забывают сказать, что исходный протокол DNS предусматривает два типа меток, которые определяются первыми двумя битами:
00 (стандартная метка) — значит, остальные 6 бит определяют длину метки, за которым следует данное количество октетов. Соответственно, длина метки не может быть более 63 байта (Например, nslookup выдаст сообщение «is not a legal name (label too long)» при попытке отрезолвить хост с длинной меткой). Заканчивается запись кодом 0×00.
11 (сжатая метка) — тогда последующие 14 бит определяют ссылку на начальный адрес серии меток. Как показал опыт, может так же содержать сжатую метку на другой адрес. В запросе, как правило, таких меток нет.
Так же метка может содержать значение 0×00 (нулевая длина), означает что это корневое доменное имя (root).
Максимальная длина NAME <= 255. Это создано ради простоты реализации.

QTYPE — Тип записи DNS, которую мы ищем (NS, A, TXT и т.д.).
QCLASS — Определяющий класс запроса (IN для Internet).

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

NAME — Такой же формат, что и QNAME в секции запроса.
TYPE — тип ресурсной записи. Определяет формат и назначение данной ресурсной записи.
CLASS — класс ресурсной записи; теоретически считается, что DNS может использоваться не только с TCP/IP, но и с другими типами сетей, код в поле класс определяет тип сети. В основном IN для Internet (Код 0×0001)
TTL — (Time To Live) — допустимое время хранения данной ресурсной записи в кэше неответственного DNS-сервера.
RDLENGTH — длина поля данных (RDATA).
RDATA — поле данных, формат и содержание которого зависит от типа записи.


Рассмотрим пакет из реального запроса и ответа. Запускаем любимый снифер и резолвим habrahabr.ru.

Запрос
1rp2ydf6wdmdjyp5ek0qureukya.png

Разберем структуру dns заголовка.

ID транзакции = 0×9bce

Далее идут флаги. 01 00 представим как двоичное значение 0»0000»0»0»1»0»000»0000 (здесь и далее я разделяю биты апострофом для лучшего визуального представления деления флагов)
QR=0 — значит этот пакет является запросом;
Opcode=0000 — Стандартный запрос;
AA=0 — данное поле имеет смысл только в DNS-ответах, поэтому всегда 0;
TC=0 — данное поле имеет смысл только в DNS-ответах, поэтому всегда 0;
RD=1 — Просим вернуть только IP адрес;
RA=0 — отправляется только сервером;
Z=000 — всегда нули, зарезервированное поле;
RCODE=0000 — Все прошло без ошибок

QDCOUNT=00 01 — 1 запись в секции запросов
ANCOUNT=00 00 — В запросе всегда 0, секция для ответов
NSCOUNT=00 00 — В запросе всегда 0, секция для ответов
ARCOUNT=00 00 — В запросе всегда 0, секция для ответов
Дальше у нас идет секции запросов и ответов. С одной записью.
Первым октетом у нас идет 0×09, представим его как двоичное значение 00»001001. Первые два бита идут 00, это значит что это обычная метка. Длина метки 9 байт (b001001).»68 61 62 72 61 68 61 62 72». Вот эти 9 байт. Тут написано «habrahabr» (в 16-ричном виде). Идем дальше. Октет 0×02. Первых два бита 00, значит снова обычная метка с длиной 2 байта. Вот они:»72 75». Написано «ru». Идем дальше. Октет 0×00. Значит конец записи хоста. Мы получили два слова «habrahabr» и «ru». Объединяем их точкой, получаем «habrahabr.ru», это и есть хост который мы запросили.
QTYPE=0×0001 — Соответствует типу A (запрос адреса хоста)
QCLASS=0×0001 — Соответствует классу IN.

Ответ
mxockspvfkwpgsyk8agkrzsqvps.png

Разберем структуру dns заголовка.
ID транзакции = 0×9bce. Она должна быть ровна ID от запроса.
Снова флаги. 81 80 представим как двоичное значение 1»0000»0»0»1»1»000»0000
QR=1 — значит этот пакет является ответом;
Opcode=0000 — Стандартный запрос;
AA=0 — Сервер не является авторитетным для домена;
TC=0 — Вся информация поместилась в один пакет;
RD=1 — Просим вернуть только IP адрес;
RA=1 — Сервер поддерживает рекурсию;
Z=000 — всегда нули, зарезервированное поле;
RCODE=0000 — Все прошло без ошибок

QDCOUNT=00 01 — 1 запись в секции запросов
ANCOUNT=00 01 — Теперь у нас появилась одна запись в ответе
NSCOUNT=00 00 — В запросе всегда 0, секция для ответов
ARCOUNT=00 00 — В запросе всегда 0, секция для ответов

Дальше у нас идет секции запросов и ответов. С двумя записями. Одна запись запроса, другая запись с ответом. Расписывать секцию запрос я не буду, он будет всегда 1в1 такой же, как и в пакете запроса. Приступим с секции ответов.
Первым октетом у нас идет 0×09, два первых бита 00, значит обычная метка с длиной 9 байт. Читаем 9 байт, получаем «HABRAHABR». Далее идет 0XC0 (b11000000). Как видим, первые два бита имеют значение 11, это значит что перед нами сжатая ссылка. Смотрим следующие 8 бит (это у нас 0×16 (b00010110)) и объединяем с текущими последними 6 бит. Получаем b00000000010110. Ссылка на 22ой байт пакета DNS (02 72 75 00). Начиная с 22 октета, снова получаем метки. По тем же правилам. У нас это получается ».ru». Объединяем всё что получили, получается «HABRAHABR.ru» Это и есть хост о котором пойдет дальше речь.

QTYPE = 0×0001 — Соответствует типу A (запрос адреса хоста)
QCLASS = 0×0001 — Соответствует классу IN.
TTL = 0×00000c90 — время актуальности данных 3216 секунд.
RDLENGTH = 0×0004 — Длина данных 4 октета.
RDATA = «b2 f8 ed 44».
Как уже было сказано, формат и содержание зависит от типа записи. Тип записи у нас «A». Значит, чтобы получить IP мы должны считать 4 байта. Каждый байт это и будет соответствующий октет IP адреса, записанный в 16ричном виде.
Получаем IP: b2.f8.ed.44 или »178.248.237.68». Что и требовалось получить.

Например, для типа NS, формат был бы такой

    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    /                   NSDNAME                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+


И считывали мы бы имя по правилам QNAME.

© Habrahabr.ru