Предисловие
Решил как то написать снифер 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 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.
Автор: admsasha