Хабрахабр

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

Решил как то написать снифер 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 бит) — данный флаг устанавливается в пакете ответе в том случае если сервер не смог поместить всю необходимую информацию в пакет из-за существующих ограничений.

Если он флаг устанавливается в запросе — это значит, что клиент просит сервер не сообщать ему промежуточных ответов, а вернуть только IP-адрес. RD (1 бит) — этот однобитовый флаг устанавливается в запросе и копируется в ответ.

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 предусматривает два типа меток, которые определяются первыми двумя битами:

Соответственно, длина метки не может быть более 63 байта (Например, nslookup выдаст сообщение “is not a legal name (label too long)” при попытке отрезолвить хост с длинной меткой). 00 (стандартная метка) – значит, остальные 6 бит определяют длину метки, за которым следует данное количество октетов. Как показал опыт, может так же содержать сжатую метку на другой адрес. Заканчивается запись кодом 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 – Все прошло без ошибок Далее идут флаги.

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

Ответ

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

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

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

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

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

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

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

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

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

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»