Pull to refresh

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

Reading time 7 min
Views 37K

Предисловие


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

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

Структура пакета 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 — дополнительные записи, которые относятся к запросу, но не являются строго ответами на вопрос.

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

Структура заголовка DNS


                                    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
      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)” при попытке отрезолвить хост с длинной меткой). Заканчивается запись кодом 0x00.
11 (сжатая метка) – тогда последующие 14 бит определяют ссылку на начальный адрес серии меток. Как показал опыт, может так же содержать сжатую метку на другой адрес. В запросе, как правило, таких меток нет.

Так же метка может содержать значение 0x00 (нулевая длина), означает что это корневое доменное имя (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 (Код 0x0001)
TTL — (Time To Live) — допустимое время хранения данной ресурсной записи в кэше неответственного DNS-сервера.
RDLENGTH — длина поля данных (RDATA).
RDATA — поле данных, формат и содержание которого зависит от типа записи.

Практика


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

Запрос



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

ID транзакции = 0x9bce

Далее идут флаги. 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, секция для ответов
Дальше у нас идет секции запросов и ответов. С одной записью.
Первым октетом у нас идет 0x09, представим его как двоичное значение 00’001001. Первые два бита идут 00, это значит что это обычная метка. Длина метки 9 байт (b001001). “68 61 62 72 61 68 61 62 72”. Вот эти 9 байт. Тут написано “habrahabr” (в 16-ричном виде). Идем дальше. Октет 0x02. Первых два бита 00, значит снова обычная метка с длиной 2 байта. Вот они: “72 75”. Написано “ru”. Идем дальше. Октет 0x00. Значит конец записи хоста. Мы получили два слова “habrahabr” и “ru”. Объединяем их точкой, получаем “habrahabr.ru”, это и есть хост который мы запросили.
QTYPE=0x0001 – Соответствует типу A (запрос адреса хоста)
QCLASS=0x0001 – Соответствует классу IN.

Ответ



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

ID транзакции = 0x9bce. Она должна быть ровна 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 такой же, как и в пакете запроса. Приступим с секции ответов.

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

QTYPE = 0x0001 – Соответствует типу A (запрос адреса хоста)
QCLASS = 0x0001 – Соответствует классу IN.
TTL = 0x00000c90 – время актуальности данных 3216 секунд.
RDLENGTH = 0x0004 – Длина данных 4 октета.
RDATA = «b2 f8 ed 44».

Как уже было сказано, формат и содержание зависит от типа записи. Тип записи у нас “A”. Значит, чтобы получить IP мы должны считать 4 байта. Каждый байт это и будет соответствующий октет IP адреса, записанный в 16ричном виде.

Получаем IP: b2.f8.ed.44 или «178.248.237.68». Что и требовалось получить.

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

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

И считывали мы бы имя по правилам QNAME.
Tags:
Hubs:
+27
Comments 6
Comments Comments 6

Articles