Привет, Хабр! В этой статье я хочу рассказать о своем опыте создания DNS сервера. Разрабатывал я его "чисто повеселиться", при разработке будем придерживаться спецификации RFC. Сейчас по-быстрому разберемся, в чем принцип работы DNS серверов. Чтобы сейчас читать эту статью, вы зашли на Хабр, для этого в браузере вы ввели www.habr.com, браузер же переводит этот домен в ip адрес, по типу 178.248.237.68:443, чтобы сделать запрос. Домены существуют, чтобы люди не запоминали эти сложные комбинации чисел, а запоминали только привычные нам слова. DNS сервера же переводят эти домены в нормальный для компьютера вид. Простая аналогия, телефонная книжка. Вместо того, чтобы запоминать мобильные номера каждого человека, мы создаем контакт и ориентируемся по заданым именам в телефонной книжке. DNS протокол является прикладным протоколом, который работает поверх UDP. В данном протоколе сущетствуют только один формат, который называется "Сообщение". То есть DNS-запрос и DNS-ответ имеют одинаковый формат. Размер сообщения - 512 байт, согласно спецификации. Структуру сообщения разберем позже и по порядку. Для начала поднимем сервер, принимающий UDP запросы и отдающий пустые ответы, чтобы удостовериться будем просто логировать их. Код сервера Результат кода С помощью утилиты nc подключились к UDP серверу и отправили запрос. Про утилиту подробнее можно узнать здесь https://netcat.sourceforge.net/ https://habr.com/ru/articles/657613/ https://habr.com/ru/articles/678968/ https://habr.com/ru/articles/336596/ Как я указывал выше, в сообщении есть 5 секций, сейчас разберем Header (Заголовок) Размер заголовка в любом сообщении ВСЕГДА 12 байт, а числа закодированы в формате Big-Endian. Эта информация нам понадобится когда придется парсить и составлять заголовок. Также можно увидеть множество полей в заголовке, но обратим внимание на важные, по-моему мнению: ID, 16 битное значение, ID ответа всегда равен ID запроса QR, значение 1 для ответа и 0 для запроса RCODE, статус ответа, 0 (no error) QDCOUNT, количество запросов/вопросов в секции Questions в сообщении ANCOUNT, количество ответов в секции Answers в ответе В Go можем заимплементировать заголовок таким образом: Теперь, когда к нам приходит запрос, нужно распарсить заголовок и перенести данные из заголовка запроса в заголовок ответа. Для этого можем написать функцию для чтения первых 12 байт запроса: Как видно по коду, приходиться использовать побайтовые сдвиги. Все данные для полей фетчим из заголовка запроса.  Но нам также надо и закодировать сообщение в байты. Ответное сообщение кодируется сверху вниз, то есть сначала кодируем заголовок потом другие секции. Вот функция для кодировки заголовка: Битовые сдвиги наше все!Для того, чтобы не запутаться, можно вернуться к этой картинке, где указаны размеры в байтах каждого поля в загаловке После того, как мы распарсили заголовок запроса и закодировали его для ответа, надо как-то протестить то, что мы реализовали. Для этого есть маленький DNS клиент на Python После запуска скрипта и сервера в логах можно увидеть следующее.Дебагер, ну чтобы точно удостовериться) Балдеж Запросы (вопросы), как вам удобно, второе поле в каждом DNS запросе, чаще всего количество запросов равно 1, но бывает и несколько запросов/вопросов. Структура запроса имеет куда меньше полей.   Сейчас размеберем каждую в подробности QName, доменное имя, представленное в виде лейблов, например для habr.com будет два лейбла: habr и com.   QType, 16 битное число, которое показывает, что мы хотим получить. Для нашего сервера дефолтом будет значение А, потому что А - адрес хоста, полный список типов тут QClass, 16 битное число, которое показывает класс запроса, например, для нашего сервера дефолтом будет значение IN, потому что IN - the Internet полный список классов тут Но в запросе доменное имя отправляется не сплошным текстом, а кодируется в виде последовательности лейблов <length><label>, где  <length> - это один байт,  указывающий длину последующего лейбла <label> - сам лейбл \x00 - байт, который указывает на конец последовательности лейблов Пример, habr.com будет выглядить так Теперь можно приступить к имплементации  Для начала создадим тип для QClass и QType. Конечно можно было задать просто две единицы, но мне такой вариант ближе  Как и с заголовком нам нужно распарсить запрос и закодировать его для ответа А также видоизменим отправку ответа в main функции И чуток видоизменим python клиент После запуска скрипта и сервера можно снова удостовериться в работе Ответ - последнее поле, которое разберем, и очень важное, потому что именно тут будет возвращаться IP адрес хоста. В ответе мы встречаем знакомые поля, но из новых тут TTL - time-to-live, период времени в секундах, на которое может закеширироваться на сервере, размер 32 бита RDLENGHT - длина RDATA, так как IP адрес это 4 байта, то будет равно 4, размер 32 бит RDATA - значение, которое является ответом на запрос, в нашем случа IP адрес, к примеру 8.8.8.8 Пример имплементации ответа и, само собой, метод для кодировки  Так как мы не можем запарсить ответ, то мы просто прокинем создание структуры, а также создадим мапу, где будем хранить соотношение домена к его IP После запуска Python скрипта можно увидеть наш полученный IP адрес Ну подводя итоги, разработали минимальный по умениям рабочий DNS сервер.Надеюсь вам понравилась эта статья! P.S. Возможно много опечаток, не судите строго