Как настроить простой DNS-сервер для локальной сети
Если вы впервые столкнулись с необходимостью поднять DNS-сервер для локальной сети под Linux, то эта статья – для вас. Преимущество предлагаемого способа – простота: сервер можно настроить буквально за несколько минут. Но этот способ, скорее всего, не подойдёт для продакшн серверов.
Автор статьи провёл несколько часов в борьбе с ошибками, багами и непонятным поведением системы прежде чем получил стабильный результат.
Далее будем считать, что имеется локальная сеть, состоящая из нескольких хостов. Локальная сеть настроена, сетевой доступ между хостами имеется. На хостах установлен Ubuntu 18.04.4 LTS (для других версий не проверялось).
Шаг 1. Установка необходимых пакетов
Следующие шаги выполняются на хосте, на котором будет устанавливаться DNS-сервер.
-
Установите Dnsmasq: sudo apt-get install dnsmasq При установке выведутся следующие ошибки:
failed to create listening socket for port 53: Address already in use
FAILED to start up
Failed to start dnsmasq — A lightweight DHCP and caching DNS server.
При рестарте системы файл /etc/resolv.conf автоматически пересоздаётся. Поэтому если прописать в него нужный адрес вручную, то изменения окажутся стёртыми после перезапуска. По умолчанию после перезапуска в этот файл прописывается адрес 127.0.0.53 , который используется сервисом systemd-resolve . Этот сервис осуществляет определение IP-адресов доменов для приложений, работающих на том же хосте, на котором запущен сервис. Но мы планируем перестать использовать этот сервис и начать использовать dnsmasq .
Шаг 2. Настройка пакетов
- Отредактируйте файл /etc/dnsmasq.conf :
sudo nano /etc/dnsmasq.conf
- no-resolv Эта настройка выключает загрузку настроек из /etc/resolv.conf . Все настройки будут браться из редактируемого файла /etc/dnsmasq.conf . Это сильно упрощает конфигурацию Dnsmasq’а, поскольку файл /etc/resolv.conf автоматически пересоздаётся при рестарте системы.
- server=8.8.8.8 8.8.8.8 — это адрес DNS-сервера Гугл. Этот адрес можно заменить на любой другой адрес публичного DNS-сервера. Например, на адрес DNS-сервера вашего провайдера или ранее используемого DNS-сервера. Запросы, которые не сможет обработать Dnsmasq будут направлены на этот сервер.
- listen-address=0.0.0.0 Эта настройка позволит осуществлять запросы к Dnsmasq’у с других хостов.
- bind-interfaces Задаёт режим, при котором Dnsmasq не осуществляет привязку к интерфейсам, по которым не должна осуществляться обработка запросов. Без этой настройки в предлагаемом варианте конфигурации сервер не работает.
Шаг 3. Настройка используемых DNS-серверов
Данная настройка выполняется на всех хостах-клиентах, с которых будут отправляться запросы на хост с сервисом Dnsmasq.
Проще всего настроить используемые DNS-сервера в графическом интерфейсе. Укажите адрес хоста, на котором установлен Dnsmasq, первым в списке:

Шаг 4. Локальное тестирование DNS-сервера
Проверку настроек можно и не делать. Но если вам интересно узнать, всё ли работает правильно, то выполните следующие команды на хосте с сервисом Dnsmasq.
- Проверьте, что в файле /etc/resolve.conf прописан адрес 127.0.0.1 : cat /etc/resolve.conf
- Выполните команду: sudo netstat -tulpen Вы должны увидеть, что адрес 0.0.0.0:53 занят Dnsmasq’ом, а адрес 127.0.0.53:53 не фигурирует в списке.
- Выполните команду: dig ya.ru Вы должны получить вывод, в котором присутствует примерно такая строчка. В начале строки не должно быть символов ; . ya.ru. 220 IN A 87.250.250.242
- Выполните команду: dig myserver.tst Вы должны получить вывод, в котором присутствует примерно такая строчка: myserver.tst. 0 IN A 1.2.3.4
Шаг 5. Тестирование DNS-сервера с других хостов
Теперь можно проверить работу DNS-сервера с других хостов.
Выполните пункты 3 и 4 из предыдущего раздела. Вывод в консоль должен быть аналогичен результатам, указанным в предыдущем разделе.
Дополнительная информация
Открыть, если что-то пошло не так
- Следующая команда в режиме реального времени выводит в консоль все запросы, выполняемые на порт 53. Это помогает определить факт выполнения запросов. sudo tcpdump -l port 53 Данную команду логично выполнять в другом терминале – не в том, в который вводятся команды, подлежащие проверке.
- Обратите внимание, что DNS-запросы кэшируются и сервисом systemd-resolved, и сервисом dnsmasq. Для сброса кэша проще всего перезапустить используемый сервис: sudo systemctl restart dnsmasq (на серверном хосте) sudo systemctl restart systemd-resolved (на клиентских хостах)
Заключение
В этой статье мы рассмотрели, как можно сравнительно быстро настроить DNS-сервер для локальной сети под Linux. Если вы знаете какие-то другие фишки по настройке DNS-сервера, напишите об этом в комментариях.
События 407 и 408 регистрируются в журнале событий DNS-сервера.
В этой статье описано решение проблемы, из-за которой не удается запросить DNS-сервер под управлением Windows 2000.
Применимо к: Windows 2000
Исходный номер базы знаний: 279678
Симптомы
Вы не можете запросить DNS-сервер под управлением Windows 2000, и в журнале событий приложения для DNS-сервера будут перечислены следующие ошибки:
Идентификатор события: 407
Источник: DNS
Описание: DNS-серверу не удалось привязать сокет датаграммы (UDP) к IP_address. Данные — это ошибка.
Идентификатор события: 408
Источник: DNS
Описание: DNS-серверу не удалось открыть сокет для адреса [IP_address]. Убедитесь, что это допустимый IP-адрес на этом компьютере. Если это недопустимо, используйте диалоговое окно «Интерфейсы» в разделе «Свойства сервера» диспетчера DNS, чтобы удалить его из списка IP-интерфейсов. Затем остановите и перезапустите DNS-сервер. (Если это был единственный IP-интерфейс на этом компьютере и DNS-сервер может не запуститься в результате этой ошибки. В этом случае удалите значение DNS\Parmeters\ListenAddress в разделе служб реестра и перезапустите.) Если это допустимый IP-адрес для этого компьютера, убедитесь, что не запущено ни одно другое приложение (например, другой DNS-сервер), которое будет пытаться использовать DNS-порт.
Причина
Эти ошибки могут возникать на компьютерах, на которых на одном сервере установлены обе следующие службы:
- Преобразование сетевых адресов (NAT)
- DNS ServerNAT имеет параметр прокси-сервера DNS, который позволяет клиентам DHCP направлять запросы DNS к серверу NAT. Затем клиентские запросы DNS перенаправлять на настроенный DNS-сервер сервера NAT. Прокси-сервер DNS и служба DNS-сервера не могут сосуществовать на одном узле, если хост использует тот же интерфейс и IP-адрес с параметрами по умолчанию.
Решение
Чтобы устранить эту проблему, используйте любой из следующих методов:
- Используйте другой сервер для DNS-сервера вместо установки NAT и DNS-сервера на одном узле.
- Не используйте функции распределителя DHCP и прокси-сервера DNS в NAT (вместо этого используйте службу DHCP-сервера).
- Настройте DNS-сервер таким образом, чтобы он не прослушивал IP-адрес сетевого адаптера, который работает в качестве частного интерфейса для NAT. Для этого выполните следующие действия:
- Запустите оснастку DNS в консоли управления (MMC), щелкните правой кнопкой мыши DNS-сервер и выберите пункт «Свойства».
- Откройте вкладку «Интерфейсы», а затем в разделе «Прослушивание» установите флажок «Только следующие IP-адреса«.
- Щелкните IP-адрес, который не должен прослушивать сервер, и нажмите кнопку » Удалить».
- Нажмите кнопку « ОК» и закройте оснастку DNS.
При удалении IP-адреса из списка интерфейсов на DNS-сервере служба DNS-сервера не отвечает на запросы DNS, направленные на этот IP-адрес. Запросы DNS, которые должны быть разрешены DNS-сервером, должны быть направлены в другие интерфейсы, прослушиваемые DNS-сервером.
Состояние
Корпорация Майкрософт подтвердила, что это проблема в продуктах Майкрософт, перечисленных в начале этой статьи.
Обратная связь
Были ли сведения на этой странице полезными?
DNS over TLS — Шифруем наши DNS запросы с помощью Stunnel и Lua

источник изображения
После новости о том что «Google Public DNS тихо включили поддержку DNS over TLS» я решил попробовать его. У меня уже есть Stunnel который создаст шифрованный TCP туннель до гугла. Но программы обычно общаются с DNS по UDP протоколу. Поэтому нам нужен прокси который будет пересылать UDP пакеты в TCP поток и обратно. Мы напишем его на Lua.
Вся разница между TCP и UDP DNS пакетами:
4.2.2. TCP usage
Messages sent over TCP connections use server port 53 (decimal). The message is prefixed with a two byte length field which gives the message length, excluding the two byte length field. This length field allows the low-level processing to assemble a complete message before beginning to parse it.
То есть делаем туда:
- берём пакет из UDP
- добавляем к нему в начале пару байт в которых указан размер этого пакета
- отправляем в TCP канал
И в обратную сторону:
- читаем из TCP пару байт тем самым получаем размер пакета
- читаем пакет из TCP
- отправляем его получателю по UDP
Настраиваем Stunnel
Настройка простая. Пишем в stunnel.conf:
[dns] client = yes accept = 127.0.0.1:53 connect = 8.8.8.8:853
То есть Stunnel:
- примет не шифрованное TCP по адресу 127.0.0.1:53
- откроет шифрованный TLS тунель до адреса 8.8.8.8:853 (Google DNS)
- будет передавать данные туда и обратно
Работу тунеля можно проверить командой:
nslookup -vc ya.ru 127.0.0.1
Опция vc заставляет nslookup использовать TCP соединение к DNS серверу вместо UDP.
*** Can't find server name for address 127.0.0.1: Non-existent domain Server: UnKnown Address: 127.0.0.1 Non-authoritative answer: Name: ya.ru Address: (здесь IP яндекса)
Пишем скрипт
Я пишу на Lua 5.3. В нём уже доступны бинарные операции с числами. Ну и нам понадобится модуль Lua Socket.
local socket = require "socket" -- подключаем lua socket
Напишем простенькую функцию которая позволит отправить дамп пакета в консоль. Хочется видеть что делает прокси.
function serialize(data) -- Преобразуем символы не входящие в диапазоны a-z и 0-9 и тире в HEX представление 'xFF' return "'"..data:gsub("[^a-z0-9-]", function(chr) return ("x%02X"):format(chr:byte()) end).."'" end
UDP в TCP и обратно
Пишем две функции которые будут оперировать двумя каналами передачи данных.
-- здесь пакеты из UDP пересылаются в TCP поток function udp_to_tcp_coroutine_function(udp_in, tcp_out, clients) repeat coroutine.yield() -- возвращаем управление главному циклу packet, err_ip, port = udp_in:receivefrom() -- принимаем UDP пакет if packet then -- > - big endian -- I - unsigned integer -- 2 - 2 bytes size tcp_out:send(((">I2"):pack(#packet))..packet) -- добавляем размер пакета и отправляем в TCP local -- читаем ID пакета clients[id] = -- записываем адрес отправителя print(os.date("%c", os.time()) ,err_ip, port, ">", serialize(packet)) -- отображаем пакет в консоль end until false end -- здесь пакеты из TCP потока пересылаются к адресату по UDP function tcp_to_udp_coroutine_function(tcp_in, udp_out, clients) repeat coroutine.yield() -- возврашяем управление главному циклу -- > - big endian -- I - unsigned integer -- 2 - 2 bytes size local packet = tcp_in:receive((">I2"):unpack(tcp_in:receive(2)), nil) -- принимаем TCP пакет local -- читаем ID пакета local client = clients[id] -- находим получателя if client then udp_out:sendto(packet, client.ip, client.port) -- отправляем пакет получателю по UDP clients[id] = nil -- очищаем ячейку print(os.date("%c", os.time()) ,client.ip, client.port, "
Обе функции сразу после запуска выполняют coroutine.yield(). Это позволяет первым вызовом передать параметры функции а дальше делать coroutine.resume(co) без дополнительных параметров.
main
А теперь main функция которая выполнит подготовку и запустит главный цикл.
function main() local tcp_dns_socket = socket.tcp() -- подготавливаем TCP сокет local udp_dns_socket = socket.udp() -- подготавливаем UDP сокет local tcp_connected, err = tcp_dns_socket:connect("127.0.0.1", 53) -- соединяемся с TCP тунелем assert(tcp_connected, err) -- проверяем что соединились print("tcp dns connected") -- сообщаем что соединились в консоль local udp_open, err = udp_dns_socket:setsockname("127.0.0.1", 53) -- открываем UDP порт assert(udp_open, err) -- проверяем что открыли print("udp dns port open") -- сообщаем что UDP порт открыт -- пользуемся тем что таблицы Lua позволяют использовать как ключ что угодно кроме nil -- используем как ключ сокет чтобы при наличии данных на нём вызывать его сопрограмму local coroutines = < [tcp_dns_socket] = coroutine.create(tcp_to_udp_coroutine_function), -- создаём сопрограмму TCP to UDP [udp_dns_socket] = coroutine.create(udp_to_tcp_coroutine_function) -- создаём сопрограмму UDP to TCP >local clients = <> -- здесь будут записываться получатели пакетов -- передаём каждой сопрограмме сокеты и таблицу получателей coroutine.resume(coroutines[tcp_dns_socket], tcp_dns_socket, udp_dns_socket, clients) coroutine.resume(coroutines[udp_dns_socket], udp_dns_socket, tcp_dns_socket, clients) -- таблица из которой socket.select будет выбирать сокет готовый к получению данных local socket_list = repeat -- запускаем главный цикл -- socket.select выбирает из socket_list сокеты у которых есть данные на получение в буфере -- и возвращает новую таблицу с ними. Цикл for последовательно возвращает значения из новой таблицы for _, in_socket in ipairs(socket.select(socket_list)) do -- запускаем ассоциированную с полученным сокетом сопрограмму local ok, err = coroutine.resume(coroutines[in_socket]) if not ok then -- если сопрограмма завершилась с ошибкой то udp_dns_socket:close() -- закрываем UDP порт tcp_dns_socket:close() -- закрываем TCP соединение print(err) -- выводим ошибку return -- завершаем главный цикл end end until false end
Запускаем главную функцию. Если вдруг будет закрыто соединение мы через секунду установим его заново вызвав main.
repeat coroutine.resume(coroutine.create(main)) -- запускаем main socket.sleep(1) -- перед рестартом ждём одну секунду until false
проверяем
- Запускаем stunnel
- Запускаем наш скрипт
lua5.3 simple-udp-to-tcp-dns-proxy.lua
nslookup ya.ru 127.0.0.1
*** Can't find server name for address 127.0.0.1: Non-existent domain Server: UnKnown Address: 127.0.0.1 Non-authoritative answer: Name: ya.ru Address: (здесь IP яндекса)
Если всё нормально можно указать в настройках соедидения как DNS сервер «127.0.0.1»
заключение
Теперь наши DNS запросы под зашитой TLS.
ссылки
- RFC1035: DOMAIN NAMES — IMPLEMENTATION AND SPECIFICATION
- DNS поверх TLS
- simple-udp-to-tcp-dns-proxy.lua
Составляем DNS-запрос вручную
В этой статье мы изучим двочиный формат сообщений Domain Name Service (DNS) и напишем вручную одно сообщение. Это больше, чем вам нужно для использования DNS, но я подумал, что для развлечения и в образовательных целях интересно посмотреть, что находится под капотом.
- Написать запросы DNS в двоичном формате
- Отправить сообщение в теле датаграммы UDP с помощью Python
- Прочитать ответ от DNS-сервера
Обзор DNS
DNS используется для перевода человекочитаемых доменных имён (таких как example.com ) в машиночитаемые IP-адреса (такие как 93.184.216.34). Для использования DNS нужно отправить запрос на DNS-сервер. Этот запрос содержит доменное имя, которое мы ищем. DNS-сервер пытается найти IP-адрес этого домена в своём внутреннем хранилище данных. Если находит, то возвращает его. Если не может найти, то перенаправляет запрос на другой DNS-сервер, и процесс повторяется до тех пор, пока IP-адрес не будет найден. Сообщения DNS обычно отправляются по протоколу UDP.
Стандарт DNS описан в RFC 1035. Все диаграммы и бóльшая часть информации для этой статьи взята в данном RFC. Я бы рекомендовал обратиться к нему, если что-то непонятно.
В этой статье мы используем шестнадцатеричный формат для упрощения работы с бинарником. Ниже я добавил краткое пояснение, как они переводятся друг в друга [1] .
Формат запроса
У всех сообщений DNS одинаковый формат:
+---------------------+ | Заголовок | +---------------------+ | Вопрос | Вопрос для сервера имён +---------------------+ | Ответ | Ресурсные записи (RR) с ответом на вопрос +---------------------+ | Authority | Записи RR с указанием на уполномоченный сервер +---------------------+ | Дополнительно | Записи RR с дополнительной информацией +---------------------+
Вопрос и ответ находятся в разных частях сообщения. В нашем запросе будут секции «Заголовок» и «Вопрос».
Заголовок
У заголовка следующий формат:
0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
На этой диаграмме каждая ячейка представляет единственный бит. В каждой строчке 16 колонок, что составляет два байта данных. Диаграмма поделена на строки для простоты восприятия, но реальное сообщение представляет собой непрерывный ряд байтов.
Поскольку у запросов и ответов одинаковый формат заголовка, некоторые поля не имеют отношения к запросу и будут установлены в значение 0. Полное описание каждого из полей см. в RFC1035, раздел 4.1.1.
Для нас имеют значение следующие поля:
- ID: Произвольный 16-битный идентификатор запроса. Такой же ID используется в ответе на запрос, так что мы можем установить соответствие между ними. Возьмём AA AA.
- QR: Однобитный флаг для указания, является сообщение запросом (0) или ответом (1). Поскольку мы отправляем запрос, то установим 0.
- Opcode: Четырёхбитное поле, которое определяет тип запроса. Мы отправляем стандартный запрос, так что указываем 0. Другие варианты:
- 0: Стандартный запрос
- 1: Инверсный запрос
- 2: Запрос статуса сервера
- 3-15: Зарезервированы для будущего использования
Полный заголовок
Совместив все поля, можно записать наш заголовок в шестнадцатеричном формате:
AA AA - ID 01 00 – Параметры запроса 00 01 – Количество вопросов 00 00 – Количество ответов 00 00 – Количество записей об уполномоченных серверах 00 00 – Количество дополнительных записей
Для получения параметров запроса мы объединяем значения полей от QR до RCODE, помня о том, что не упомянутые выше поля установлены в 0. Это даёт последовательность 0000 0001 0000 0000 , которая в шестнадцатеричном формате соответствует 01 00 . Так выглядит стандартный DNS-запрос.
Вопрос
Секция вопроса имеет следующий формат:
0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
-
QNAME: Эта секция содержит URL, для которого мы хотим найти IP-адрес. Она закодирована как серия надписей (labels). Каждая надпись соответствует секции URL. Так, в адресе example.com две секции: example и com.
Для составления надписи нужно закодировать каждую секцию URL, получив ряд байтов. Надпись — это ряд байтов, перед которыми стоит байт беззнакового целого, обозначающий количество байт в секции. Для кодирования нашего URL можно просто указать ASCII-код каждого символа.
07 65 – у 'example' длина 7, e 78 61 – x, a 6D 70 – m, p 6C 65 – l, e 03 63 – у 'com' длина 3, c 6F 6D – o, m 00 - нулевой байт для окончания поля QNAME 00 01 – QTYPE 00 01 – QCLASS
В секции QNAME разрешено нечётное число байтов, так что набивка байтами не требуется перед началом секции QTYPE.
Отправка запроса
Мы отправляем наше DNS-сообщение в теле UDP-запроса. Следующий код Python возьмёт наш шестнадцатеричный DNS-запрос, преобразует его в двоичный формат и отправит на сервер Google DNS по адресу 8.8.8.8:53.
import binascii import socket def send_udp_message(message, address, port): """send_udp_message sends a message to UDP server message should be a hexadecimal encoded string """ message = message.replace(" ", "").replace("\n", "") server_address = (address, port) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: sock.sendto(binascii.unhexlify(message), server_address) data, _ = sock.recvfrom(4096) finally: sock.close() return binascii.hexlify(data).decode("utf-8") def format_hex(hex): """format_hex returns a pretty version of a hex string""" octets = [hex[i:i+2] for i in range(0, len(hex), 2)] pairs = [" ".join(octets[i:i+2]) for i in range(0, len(octets), 2)] return "\n".join(pairs) message = "AA AA 01 00 00 01 00 00 00 00 00 00 " \ "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 00 01" response = send_udp_message(message, "8.8.8.8", 53) print(format_hex(response))Можете запустить этот скрипт, скопировав код в файл query.py и запустив в консоли команду $ python query.py . У него нет никаких внешних зависимостей, и он должен работать на Python 2 или 3.
Чтение ответа
После выполнения скрипт выводит ответ от DNS-сервера. Разобьём его на части и посмотрим, что можно выяснить.
Заголовок
Сообщение начинается с заголовка, как и наше сообщение с запросом:
AA AA – Тот же ID, как и раньше 81 80 – Другие флаги, разберём их ниже 00 01 – 1 вопрос 00 01 – 1 ответ 00 00 – Нет записей об уполномоченных серверах 00 00 – Нет дополнительных записей
Преобразуем 81 80 в двоичный формат:
8 1 8 0 1000 0001 1000 0000
Преобразуя эти биты по вышеуказанной схеме, можно увидеть:
- QR = 1: Это сообщение является ответом
- AA = 0: Этот сервер не является уполномоченным для доменного имени example.com
- RD = 1: Для этого запроса желательна рекурсия
- RA = 1: На этом DNS-сервере поддерживается рекурсия
- RCODE = 0: Ошибки не обнаружены
Секция вопроса
Секция вопроса идентична такой же секции в запросе:
07 65 – у 'example' длина 7, e 78 61 – x, a 6D 70 – m, p 6C 65 – l, e 03 63 – у 'com' длина 3, c 6F 6D – o, m 00 - нулевой байт для окончания поля QNAME 00 01 – QTYPE 00 01 – QCLASS
Секция ответа
У секции ответа формат ресурсной записи:
0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| / RDATA / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
C0 0C - NAME 00 01 - TYPE 00 01 - CLASS 00 00 18 4C - TTL 00 04 - RDLENGTH = 4 байта 5D B8 D8 22 - RDDATA
-
NAME : Этой URL, чей IP-адрес содержится в данном ответе. Он указан в сжатом формате:
0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 1 1| OFFSET | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Первые два бита установлены в значение 1, а следующие 14 содержат беззнаковое целое, которое соответствует смещению байт от начала сообщения до первого упоминания этого имени.
В данном случае смещение составляет c0 0c или двоичном формате:
1100 0000 0000 1100
Расширения
Мы увидели, как составить DNS-запрос. Теперь можно попробовать следующее:
- Составить запрос для произвольного доменного имени
- Запрос на другой тип записи
- Отправить запрос с отключенной рекурсией
- Отправить запрос с доменным именем, которое не зарегистрировано
Десятичный Hex Двоичный Десятичный Hex Двоичный 0 0 0000 8 8 1000 1 1 0001 9 9 1001 2 2 0010 10 A 1010 3 3 0011 11 B 1011 4 4 0100 12 C 1100 5 5 0101 13 D 1101 6 6 0110 14 E 1110 7 7 0111 15 F 1111