Хитрости BIND. Берем контроль над DNS в свои руки
Life-Hack [Жизнь-Взлом]/ХакингBIND был и остается одной из самых популярных реализаций сервера DNS. Долгое время он был фактическим стандартом и для ответственных (authoritative), и для кэширующих серверов. Более того, до двухтысячных годов все до единого корневые сервера работали именно на нем. В этой статье мы рассмотрим ряд не слишком известных, но полезных возможностей BIND.
То десятилетие с середины девяностых по середину двухтысячных вообще было временем фактических монополий. Веб-сервер — Apache HTTPD, почтовый сервер — Sendmail, ну а сервер DNS — BIND. С тех пор появилось множество альтернатив. Например, часть корневых серверов перешли на NSD.
Однако альтернативы старым проектам часто идут по пути специализации. NSD не кэширует запросы и предназначен только для держателей доменов — он идеально подходит для корневых серверов, но совершенно не подойдет для корпоративного или провайдерского сервера. Проект PowerDNS состоит из двух независимых частей: ответственный pdns и кэширующий pdns_recursor. Проект dnsmasq — чисто кэширующий сервер для малых сетей со встроенной реализацией DHCP.
В то же время BIND универсален. Возможно, это и позволяет ему оставаться популярным. Он может и отвечать за зоны, и кэшировать запросы, и хранить записи в реляционных БД. Это, может, и не лучший проект для каждой из задач в отдельности, но зато он решает их все одновременно с весьма неплохой производительностью. Таким способом не слишком большие компании и сети учебных и испытательных лабораторий вполне могут обойтись одним сервером DNS.
Проверяем синтаксис настроек
При работе с серверами вручную многие люди правят файл настроек, перезапускают сервис и бегут чинить, если он не запустился. И совершенно зря, потому что у многих проектов есть опции демона или отдельные утилиты для проверки синтаксиса! В их числе и BIND.
Утилиты named-checkconf
и named-checkzone
весьма полезны как при ручной настройке, так и при написании скриптов. Будем честными — грамматика файлов BIND не всегда интуитивна. С помощью этих утилит можно получить более-менее внятные сообщения об ошибках без перезапуска сервиса.
$ named-checkconf /tmp/new-named.conf || echo "Bad file" /etc/named.conf:66: missing ';' before '}' Bad file $ named-checkzone example.com /var/named/data/example.com zone example.com/IN: NS 'ns1.example.com' has no address records (A or AAAA) zone example.com/IN: not loaded due to errors.
Ускоряем загрузку зон
Текстовый формат зон BIND стал фактическим стандартом, и его поддерживают многие другие реализации, прежде всего чтобы упростить перенос существующих зон. Некоторые регистраторы даже позволяют редактировать и вносить записи в этом формате через веб-интерфейс. Формат www IN A 192.0.2.1
видели даже далекие от DNS люди.
INFO
Ключевое слово IN
— это не предлог in, а сокращение от Internet. Протокол DNS и BIND появились до того, как исследовательские сети объединились в единый Интернет, поэтому в DNS до сих пор формально существуют классы записей IN (Internet) и CH (ChaosNet).
Однако этот формат не слишком прост для синтаксического разбора — и загрузка зон с большим числом записей может ощутимо замедлить запуск демона. Регистраторы доменов и хостеры нередко хранят записи в базе данных, что и быстрее, и проще в управлении из админки.
Но если проблема только в производительности, есть и более простой способ: использовать двоичные форматы файлов.
Конвертировать зону в двоичный формат можно командой named-compilezone
. Формата два: map и raw. Формат map представляет собой простой дамп памяти. Он загружается быстрее всего, поскольку вообще не требует разбора, но не является переносимым — работает только в пределах одной архитектуры. Формат raw загружается чуть медленнее, но работает на любой машине.
Рассмотрим пример для формата map. Сначала нужно скомпилировать в него текстовый файл зоны.
$ sudo named-compilezone -Fmap -o /var/named/data/example.com.map example.com /var/named/data/example.com
Затем нужно соответствующим образом поправить описание зоны в /etc/named.conf
. Формат файла с данными зоны не определяется автоматически, поэтому нам придется указать опцию masterfile-format
.
zone "example.com" { type master; masterfile-format map; file "/var/named/data/example.com.map"; };
Ощутимая разница заметна только на очень больших файлах. Но если ты окажешься в такой ситуации, теперь ты знаешь, как из нее выйти.
Генерируем записи для диапазонов адресов
Часто в ответах на обратные запросы можно увидеть адреса вроде 192.0.2.10.customers.example.com
. Такие записи удобны для админов других сетей — из вывода traceroute или tcpdump сразу понятно, какой компании принадлежит адрес.
Как они делаются? Можно сгенерировать их скриптом и записать в файл зоны, но в BIND для этой цели есть встроенный механизм — директива $GENERATE
.
Для примера сгенерируем записи вида 192.0.2.X.users.example.com для диапазона адресов 192.0.2.1–100.
Сначала добавим обратную зону для сети 192.0.2.0/24 в /etc/named.conf
.
zone "2.0.192.in-addr.arpa" { type master; file "/var/named/data/2.0.192.in-addr.arpa"; };
Затем создадим файл зоны.
$TTL 86400 @ IN SOA localhost. root.localhost. ( 2020071001 ; serial 28800 ; refresh 14400 ; retry 3600000 ; expire 86400 ; default TTL ) 2.0.192.in-addr.arpa. IN NS ns1.example.com. 200 IN PTR jrandomhacker.users.example.com. $GENERATE 1-100 $ PTR 192.0.2.$.users.example.com.
Применим изменения командой rndc reload
или перезапуском сервиса. Для начала убедимся, что зона работает, и запросим PTR к настроенному вручную 192.0.2.200.
$ host 192.0.2.200 localhost 200.2.0.192.in-addr.arpa domain name pointer jrandomhacker.users.example.com.
Теперь проверим адрес, для которого нет явной записи.
$ host 192.0.2.76 localhost 76.2.0.192.in-addr.arpa domain name pointer 192.0.2.76.users.example.com.
Как видим, все работает как ожидалось.
Разберем директиву на части. Первый аргумент (1-100
) — это диапазон значений счетчика. Символ $
при генерации записей заменяется на текущее значение счетчика. В нашем случае BIND сгенерирует 99 правил от $=1
до $=100
.
Второй аргумент — имя записи. Для записей типа PTR это номер хоста в сети, и он совпадает со счетчиком, потому и просто $
. Третий аргумент — тип записи. Наконец, последний аргумент — ее значение.
Увы, в BIND нет способа исключить из автоматической генерации адреса, для которых уже есть явные записи, так что для клиентов со статическими адресами нужно либо выделить отдельный диапазон, либо смириться с двумя записями для одного адреса — явной и автоматически сгенерированной (что, впрочем, не принесет никакого вреда).
Директива $GENERATE
обрабатывается при загрузке зоны и генерирует все записи в памяти. Таким образом, с ее помощью можно сэкономить время админа и число строк в файле зоны, но требования к памяти останутся теми же.
Автоматически сгенерированные записи обычно ассоциируются с обратными зонами, но не ограничиваются ими.
Предположим, у тебя есть пять веб-серверов с адресами 203.0.113.10–15. Ты хочешь создать для них записи вида www[1-5].example.com
. Это вполне можно сделать автоматически. Добавь вот такую запись в файл зоны example.com:
$GENERATE 1-5 www$ A 203.0.113.${10,0,d}
Здесь 10 это константа, которая добавляется к значению счетчика, 0 — число ведущих нулей в значении (мы генерируем IP-адрес и дополнение нулями нам не нужно), d
— десятичный формат вывода.
Split DNS
Представь себе следующую распространенную ситуацию. Есть сеть, положим 10.0.0.0/24, в ней — веб-сервер 10.0.0.100 и рабочая станция 10.0.0.200. Есть маршрутизатор с внешним адресом 203.0.113.1. Порты 80/443 проброшены внутрь на 10.0.0.100.
Если создать запись вроде www.example.com. IN A 203.0.113.1
, то запросы из внешних сетей на https://www.example.com будут отлично работать. Увы, без дополнительных мероприятий не будут работать запросы на этот адрес из внутренней сети.
Чаще всего эту проблему решают с помощью так называемого hairpin NAT. Об этом я уже когда-то писал. Решение с hairpin — приемлемое, но не идеальное. Трафик с этом случае идет через маршрутизатор, даже если оба хоста находятся в одном сегменте канального уровня и могли бы общаться напрямую.
Популярность решения с hairpin объяснима. Многие сети вообще не имеют своих серверов DNS, в этом случае других решений и нет. Даже если свои сервера есть, правило для hairpin сетевой администратор может настроить без взаимодействия с администратором сервера DNS. Тем не менее правильное решение этой задачи — использовать split DNS, то есть выдавать разный ответ в зависимости от того, кто спрашивает.
Рассмотрим пример настройки. Для простоты тестирования в пределах одной машины мы используем адрес 127.0.0.10 в качестве условной «внутренней сети» — под localhost отведена вся сеть 127.0.0.0/8, а не только общеизвестный 127.0.0.1.
Чтобы выдавать разные ответы, нужно создать несколько «представлений» (view) в /etc/named.conf
. У каждого view должна быть прописана опция match-clients
, которая принимает либо имя ACL, либо ключевое слово any
.
Важный момент: если ты используешь split DNS, то все зоны нужно поместить в какое-то представление, даже если они используются только в одном представлении. Иначе демон откажется загружать настройки.
acl internal-network { 127.0.0.10/32; }; view "internal" { match-clients { internal-network; }; zone "example.com" { type master; file "/var/named/data/example.com-internal"; }; }; view "external" { match-clients { any; }; zone "example.com" { type master; file "/var/named/data/example.com"; }; };
Для тестирования воспользуемся утилитой dig, опять же из пакета BIND. Она позволяет указать адрес источника опцией -b
. Убедимся, что записи для www.example.com в файлах example.com
и example.com-internal
разные, и выполним запросы сначала с 127.0.0.1, затем с 127.0.0.10.
$ cat /var/named/data/example.com | grep www
www IN A 203.0.113.1
$ cat /var/named/data/example.com-internal | grep www
www IN A 10.0.0.100
$ dig @127.0.0.1 www.example.com a
;; ANSWER SECTION: www.example.com. 86400 IN A 203.0.113.1
$ dig -b127.0.0.10 @127.0.0.1 www.example.com a
;; ANSWER SECTION: www.example.com. 86400 IN A 10.0.0.100
Как видим, во втором случае нам выдали в ответ внутренний адрес, что нам и нужно.
Заключение
Кто-то скажет, что свой сервер DNS больше не нужен и все можно сделать через админку регистратора. Но если у тебя сколько-нибудь сложная внутренняя сеть, это совершенно не так. Свой сервер может сделать сеть более удобной и управляемой, и BIND поможет тебе в этом.