«Cоздание нескольких потоков и управление ими в Python» и «DNS сервер BIND с локальными зонами»: разница между страницами

Материал из support.qbpro.ru
(Различия между страницами)
imported>Vix
(Новая страница: «== О потоках управления == В современной операционной системе, даже не выполняющей ничег…»)
 
imported>Vix
Нет описания правки
 
Строка 1: Строка 1:
== О потоках управления ==
Исходные данные


Для корректной работы DNS нем необходимо иметь настроенную сеть. DNS в текущей статье будет настроен на дистрибутиве Debian, особенности других дистрибутивов тоже будут отмечены. Конфиг сети стенда следующий:


В современной операционной системе, даже не выполняющей ничего особенного, могут одновременно работать несколько процессов (processes). Например, при запуске программы запускается новый процесс. Функции для управления процессами можно найти в стандартном модуле os языка Python. Здесь же речь пойдет о потоках.
dns:~# cat /etc/network/interfaces
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
  address 10.0.0.152
  netmask 255.255.255.0
  gateway 10.0.0.254
auto eth1
iface eth1 inet static
  address 192.168.1.1
  netmask 255.255.255.0
где 10.0.0.152/24 - внешний интерфейс (подсеть, выделенная провайдером), 192.168.1.1/24 - внутренний (Локальная сеть). Настраиваемая зона будет иметь имя example.com. В примере со slave сервером, вторичный сервер будет расположен на IP 10.0.0.191.


Потоки управления (threads) образуются и работают в рамках одного процесса. В однопоточном приложении (программе, которая не использует дополнительных потоков) имеется только один поток управления. Говоря упрощенно, при запуске программы этот поток последовательно исполняет встречаемые в программе операторы, направляясь по одной из альтернативных ветвей оператора выбора, проходит через тело цикла нужное число раз, выбирается к месту обработки исключения при возбуждении исключения. В любой момент времени интерпретатор Python знает, какую команду исполнить следующей. После исполнения команды становится известно, какой команде передать управление. Эта ниточка непрерывна в ходе выполнения программы и обрывается только по ее завершении.
Установка BIND9


Теперь можно представить себе, что в некоторой точке программы ниточка раздваивается, и каждый поток идет своим путем. Каждый из образовавшихся потоков может в дальнейшем еще несколько раз раздваиваться. (При этом один из потоков всегда остается главным, и его завершение означает завершение всей программы.) В каждый момент времени интерпретатор знает, какую команду какой поток должен выполнить, и уделяет кванты времени каждому потоку. Такое, казалось бы, незначительное усложнение механизма выполнения программы на самом деле требует качественных изменений в программе - ведь деятельность потоков должна быть согласована. Нельзя допускать, чтобы потоки одновременно изменяли один и тот же объект, результат такого изменения, скорее всего, нарушит целостность объекта.
Для работы DNS сервера необходимо установить пакет bind9 (в некоторых дистрибутивах - bind). Как отмечено на схеме - основным конфигурационным файлом BIND является файл named.conf (данный файл может быть размещен в каталоге /etc, иногда в /etc/bind ).


Одним из классических средств согласования потоков являются объекты, называемые семафорами. Семафоры не допускают выполнения некоторого участка кода несколькими потоками одновременно. Самый простой семафор - замок (lock) или mutex (от английского mutually exclusive, взаимоисключающий). Для того чтобы поток мог продолжить выполнение кода, он должен сначала захватить замок. После захвата замка поток выполняет определенный участок кода и потом освобождает замок, чтобы другой поток мог его получить и пройти дальше к выполнению охраняемого замком участку программы. Поток, столкнувшись с занятым другим потоком замком, обычно ждет его освобождения.
Параметры (синтаксис) named.conf


Поддержка многопоточности в языке Python доступна через использование ряда модулей. В стандартном модуле threading определены нужные для разработки многопоточной (multithreading) программы классы: несколько видов семафоров (классы замков Lock, RLock и класс Semaphore ) и другие механизмы взаимодействия между потоками (классы Event и Condition ), класс Timer для запуска функции по прошествии некоторого времени. Модуль Queue реализует очередь, которой могут пользоваться сразу несколько потоков. Для создания и (низкоуровневого) управления потоками в стандартном модуле thread определен класс Thread.
Синтаксис файла named.conf придерживается следующих правил:


IP-адреса - список IP должен быть разделен символом ";" , возможно указывать подсеть в формате 192.168.1.1/24 или 192.168.1.1/255.255.255.0, (для исключения IP перед ним нужно поставить знак !), возможно указывать имена "any", "none", "localhost" в двойных кавычках.


=== Пример многопоточной программы ===
Комментарии - строки начинающиеся на #, // и заключенные в /* и */ считаются комментариями.


В файлах описания зон - символ @ является "переменной" хранящей имя зоны, указанной в конфигурационном файле named.conf или в директиве @ $ORIGIN текущего описания зоны.


В следующем примере создается два дополнительных потока, которые выводят на стандартный вывод каждый свое:
Каждая завершенная строка параметров должна завершаться символом ; .


import threading
Раздел Acl
            def proc(n):
Acl (access control list) - позволяет задать именованный список сетей. Формат раздела: acl "имя_сети" {ip; ip; ip; };
              print "Процесс", n
            p1 = threading.Thread(target=proc, name="t1", args=["1"])
            p2 = threading.Thread(target=proc, name="t2", args=["2"])
            p1.start()
            p2.start()
Сначала получается два объекта класса Thread, которые затем и запускаются с различными аргументами. В данном случае в потоках работает одна и та же функция proc(), которой передается один аргумент, заданный в именованном параметре args конструктора класса Thread. Нетрудно догадаться, что метод start() служит для запуска нового потока. Таким образом, в приведенном примере работают три потока: основной и два дополнительных (с именами "t1" и "t2" ).


== Функции модуля threading ==
Раздел Options
Раздел Options задает глобальные параметры конфигурационного файла, управляющие всеми зонами. Данный раздел имеет формат: options {операторы_раздела_Options};. Options может быть "вложен" в раздел Zone, при этом он переопределяет глобальные параметры. Часто используемые операторы options:


allow-query {список_ip} - Разрешает ответы на запросы только из список_ip. При отсутствии - сервер отвечает на все запросы.
allow-recursion {список_ip} - На запросы из список_ip будут выполняться рекурсивные запросы. Для остальных - итеративные. Если  не задан параметр, то сервер выполняет рекурсивные запросы для всех сетей.
allow-transfer {список_ip} - Указывает список серверов, которым разрешено брать зону с сервера (в основном тут указывают slave сервера)
directory /path/to/work/dir - указывает абсолютный путь к рабочему каталогу сервера. Этот оператор допустим только в разделе  options.
forwarders {ip порт, ip порт...} - указывает адреса хостов и если нужно порты, куда переадресовывать запросы (обычно тут указываются DNS провайдеров ISP).
forward ONLY или forward FIRST - параметр first указывает, DNS-серверу пытаться разрешать имена с помощью DNS-серверов, указанных в параметре forwarders, и лишь в случае, если разрешить имя с помощью данных серверов не удалось, то будет осуществлять попытки разрешения имени самостоятельно.
notify YES|NO - YES - уведомлять slave сервера об изменениях в зоне, NO - не уведомлять.
recursion YES|NO - YES - выполнять рекурсивные запросы, если просит клиент, NO - не выполнять (только итеративные запросы). Если ответ найден в кэше, то возвращается из кэша. (может использоваться только в разделе Options)
Раздел Zone
Определяет описание зон(ы). Формат раздела: zone {операторы_раздела_zone}; Операторы, которые наиболее часто используются:


В модуле threading, который здесь используется, есть функции, позволяющие получить информацию о потоках:
allow-update {список_ip} - указывает системы, которым разрешено динамически обновлять данную зону.
file "имя_файла" - указывает путь файла параметров зоны (должен быть расположен в каталоге, определенном в разделе options оператором directory)
masters {список_ip} -указывает список мастер-серверов. (допустим только в подчиненных зонах)
type "тип_зоны" - указывает тип зоны, описываемой в текущем разделе,тип_зоны может принимать следующие значения:
forward - указывает зону переадресации, которая переадресовывает запросы, пришедшие в эту зону.
hint - указывает вспомогательную зону (данный тип содержит информацию о корневых серверах, к которым сервер будет обращаться в случае невозможности найти ответ в кэше)
master - указывает работать в качестве мастер сервера для текущей зоны.
slave - указывает работать в качестве подчиненного сервера для текущей зоны.
Дополнительные параметры конфигурации
Значения времени в файлах зон по умолчанию указывается в секундах, если за ними не стоит одна из следующих букв: S - секунды, M - минуты, H- часы, D - дни, W - недели. Соответственно, запись 2h20m5s будет иметь значение 2 часа 20 минут 5 секунд и соответствовать 8405 секунд.


activeCount() Возвращает количество активных в настоящий момент экземпляров класса Thread. Фактически, это len(threading.enumerate()).
Любое имя хоста/записи, не оканчивающиеся точкой считается неFQDN именем и будет дополнено именем текущей зоны. Например, запись domen в файле зоны examle.com будет развернуто в FQDN-имя domen.examle.com. .
currentThread() Возвращает текущий объект-поток, то есть соответствующий потоку управления, который вызвал эту функцию. Если поток не был создан через модуль threading, будет возвращен объект-поток с сокращенной функциональностью (dummy thread object).
enumerate() Возвращает список активных потоков. Завершившиеся и еще не начатые потоки не входят в список.
Класс Thread


Экземпляры класса threading.Thread представляют потоки Python-программы. Задать действия, которые будут выполняться в потоке, можно двумя способами: передать конструктору класса исполняемый объект и аргументы к нему или путем наследования получить новый класс с переопределенным методом run(). Первый способ был рассмотрен в примере выше. Конструктор класса threading.Thread имеет следующие аргументы:
В конфигурационных файлах BIND могут применяться следующие директивы:


Thread(group, target, name, args, kwargs)
$TTL - определяет TTL по-умолчанию для всех записей в текущей зоне.
Здесь group - группа потоков (пока что не используется, должен быть равен None ), target - объект, который будет вызван в методе run(), name - имя потока, args и kwargs - последовательность и словарь позиционных и именованных параметров (соответственно) для вызова заданного в параметре target объекта. В примере выше были использованы только позиционные параметры, но то же самое можно было выполнить и с применением именованных параметров:
$ORIGIN - изменяет имя зоны с указанного в файле named.conf. При этом, область действия данной директивы не распространяется  "выше" (то есть если файл включен директивой $INCLUDE, то область действия$ORIGN не распространяется на родительский)
$INCLUDE - включает указанный файл как часть файла зоны.
Для того чтобы локальный резолвер сервера тоже использовал локальный DNS, необходимо привести файл resolv.conf к следующему виду:


  import threading
  dns:~# cat /etc/resolv.conf
            def proc(n):
nameserver 127.0.0.1
              print "Процесс", n
Если в имени ресурсной записи встречается символ "*", то это он означает что вместо него можно подразумевать любую разрешенную последовательность символов. Такую запись называют "wildcard запись". Однако, символ "*" не может быть использован где угодно. Это может быть только первый символ в поле Name текущего домена, отделенный от остальных символом "."
            p1 = threading.Thread(target=proc, name="t1", kwargs={"n": "1"})
            p2 = threading.Thread(target=proc, name="t2", kwargs={"n": "2"})
            p1.start()
            p2.start()


То же самое можно проделать через наследование от класса threading.Thread с определением собственного конструктора и метода run():
Настройка кэширующего DNS сервера


import threading
После установки bind, он полностью готов работать как кэширующий DNS сервер без дополнительной настройки. Единственный недостаток - он обрабатывает запросы на всех интерфейсах, что нам абсолютно не нужно, поэтому мы немного подредактируем настройки сервера.
            class T(threading.Thread):
              def __init__(self, n):
                threading.Thread.__init__(self, name="t" + n)
                self.n = n
              def run(self):
                print "Процесс", self.n
            p1 = T("1")
            p2 = T("2")
            p1.start()
            p2.start()


Самое первое, что необходимо сделать в конструкторе - вызвать конструктор базового класса. Как и раньше, для запуска потока нужно выполнить метод start() объекта-потока, что приведет к выполнению действий в методе run().
Для того, чтобы BIND работал в качестве кэширующего сервера, необходимо иметь конфигурационные файлы заполненные необходимой информацией:


Жизнью потоков можно управлять вызовом методов:
named.conf;
описание серверов корневой зоны (зона типа hint);
описание зоны 127.in-addr.arpa.
dns:~# cat /etc/bind/named.conf
acl "lan" {
            192.168.1.1/24;
            127.0.0.1;
};
options {
            directory "/var/cache/bind";
          // If there is a firewall between you and nameservers you want
          // to talk to, you may need to fix the firewall to allow multiple
          // ports to talk.  See http://www.kb.cert.org/vuls/id/800113
          /*
          * Тут сказано, что если используется фаерволл, то необходимо
          *  нашему серверу создать соответствующие правила
          *  то есть открыть доступ по 53 TCP и UDP порту
          */
          forward first;              // задаем пересылку только первого запроса
          forwarders {                // указываем DNS сервера для пересылки
                      83.239.0.202;    // предоставленные провайдером
                      213.132.67.110;  // ибо до них ближе чем до корневых
          };
          listen-on { lan; };        // пусть слушает только нужные интерфейсы
          allow-query { lan; };      // разрешить запросы только из локальной сети
          allow-recursion { lan; };  // рекурсивные запросы тоже только из локальной
          allow-transfer { none; };  // трансфер зон нам не нужен
          version "unknown";        // не отображать версию DNS сервера при ответах
          auth-nxdomain no;    # для совместимости RFC1035
          listen-on-v6 { none; };    //IPv6 нам не нужен
          };
// описание настроек корневых серверов
zone "." {
          type hint;
          file "db.root";
};
// нижеописанные зоны определяют сервер авторитетным для петлевых
// интерфейсов, а так же для броадкаст-зон (согласно RFC 1912)
zone "localhost" {
          type master;
          file "localhost";
};
zone "127.in-addr.arpa" {
          type master;
          file "127.in-addr.arpa";
};
zone "0.in-addr.arpa" {
          type master;
          file "0.in-addr.arpa";
};
zone "255.in-addr.arpa" {
          type master;
          file "255.in-addr.arpa";
};


*start() Дает потоку жизнь.
В данном примере приведен кэширующий DNS сервер, обрабатывающий запросы из списка сетей lan, в которую входит только одна локальная сеть 192.168.1.1/24 и петлевой интерфейс. При необходимости можно включить туда и другие сети. После определения списка сетей в директиве acl, в любом месте конфига можно будет ссылаться на этот список по имени (в нашем примере имя - lan), что, собственно и сделано в разделе options. Большинство параметров я прокомментировал, но отдельного внимания требует раздел, описывающий зону корневых серверов. В параметре file задан относительный путь к файлу описания корневых серверов (путь, относительно рабочего каталога сервера). За обновлениями данного файла необходимо следить, хотя он обновляется довольно редко (откуда брать обновленный файл я писал в теории DNS). Как вы заметили, имеется так же две записи для зоны localhost и две записи обратных зон для бродкаст доменов. Назначение этих зон состоит в том, чтобы избежать трансляции случайных запросов имен соответствующих IP-адресов на серверы, обслуживающие корневую зону.
*run() Этот метод представляет действия, которые должны быть выполнены в потоке.
*join([timeout]) Поток, который вызывает этот метод, приостанавливается, ожидая завершения потока, чей метод вызван. Параметр timeout (число с плавающей точкой) позволяет указать время ожидания (в секундах), по истечении которого приостановленный поток продолжает свою работу независимо от завершения потока, чей метод join был вызван. Вызывать join() некоторого потока можно много раз. Поток не может вызвать метод join() самого себя. Также нельзя ожидать завершения еще не запущенного потока. Слово "join" в переводе с английского означает "присоединить", то есть, метод, вызвавший join(), желает, чтобы поток по завершении присоединился к вызывающему метод потоку.
*getName() Возвращает имя потока. Для главного потока это "MainThread".
*setName(name) Присваивает потоку имя name.
*isAlive() Возвращает истину, если поток работает (метод run() уже вызван, но еще не завершился).
*isDaemon() Возвращает истину, если поток имеет признак демона. Программа на Python завершается по завершении всех потоков, не являющихся демонами. Главный поток демоном не является.
*setDaemon(daemonic) Устанавливает признак daemonic того, что поток является демоном. Начальное значение этого признака заимствуется у потока, запустившего данный. Признак можно изменять только для потоков, которые еще не запущены.


В модуле Thread пока что не реализованы возможности, присущие потокам в Java (определение групп потоков, приостановка и прерывание потоков извне, приоритеты и некоторые другие вещи), однако они, скорее всего, будут созданы в недалеком будущем.
Чтобы не вносить неразбериху в куче конфигурационных файлов, в статье я привожу примеры на основе единого конфигурационного файла. На  самом деле, в последних версиях Debian других дистрибутивах Linux), файл named.conf выглядит следующим образом:


== Таймер ==
root@master:~# cat /etc/bind/named.conf
// This is the primary configuration file for the BIND DNS server named.
//
// Please read /usr/share/doc/bind9/README.Debian.gz for information on the
// structure of BIND configuration files in Debian, *BEFORE* you customize
// this configuration file.
//
// If you are just adding zones, please do that in /etc/bind/named.conf.local


include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";
include "/etc/bind/named.conf.default-zones";
То есть основной файл не содержит конфигураций, а включает в себя более узко специализированные файлы, которые отвечают за свои задачи, например named.conf.options - содержит глобальные параметры конфигурации, named.conf.default-zones - содержит описание localhost и broadcast зон, а named.conf.local содержит описания зон, за которые отвечает данный сервер.


Класс threading.Timer представляет действие, которое должно быть выполнено через заданное время. Этот класс является подклассом класса threading.Thread, поэтому запускается также методом start(). Следующий простой пример, печатающий на стандартном выводе Hello, world! поясняет сказанное:


def hello():
Далее, хочу обратить внимание на наличие файлов зон в каталоге, указанном в разделе options в параметре directory с именами, соответствующими параметрам file в разделах, описывающих зоны:
              print "Hello, world!"
            t = Timer(30.0, hello)
            t.start()


dns:~# ls -l /var/cache/bind/
итого 24
-rw-r--r-- 1 root root  237 Май 28 01:28 0.in-addr.arpa
-rw-r--r-- 1 root root  271 Май 28 01:28 127.in-addr.arpa
-rw-r--r-- 1 root root  237 Май 28 01:28 255.in-addr.arpa
-rw-r--r-- 1 root root 2994 Май 28 01:28 db.root
-rw-r--r-- 1 root root  270 Май 28 01:28 localhost
dns:~# cat /var/cache/bind/127.in-addr.arpa
;
; BIND reverse data file for local loopback interface
;
$TTL    604800
@      IN      SOA    localhost. root.localhost. (
                              1        ; Serial
                              604800    ; Refresh
                              86400    ; Retry
                              2419200  ; Expire
                              604800 )  ; Negative Cache TTL
;
@      IN      NS      localhost.
1.0.0  IN      PTR    localhost.
Рассматривать файлы "петлевых" и бродкастовых зон не вижу смысла, т.к. после установки пакета bind настройки заданные по умолчанию в данных файлах вполне приемлемы. Далее, при организации мастер сервера мы рассмотрим пример описания файла зоны. Хочу обратить внимание, что мы настраиваем кэширующий сервер, а определяем мы его и как master для некоторых из зон. В нашем случае "кэширующий" говорит о том, что наш сервер не поддерживает ни одну из реально существующих зон, т.е. ему не делегировано прав на такое обслуживание.


== Замки ==
Да, чуть не забыл, демон named должен быть разрешен для запуска на необходимых уровнях выполнения ОС (команда в RedHat - /sbin/chkconfig bind9 on, в Debian - /usr/sbin/update-rc.d bind9 defaults). После изменения конфигурационных файлов можно добавить сервис в автозагрузку и запустить демон:


dns:~# update-rc.d bind9 defaults
Adding system startup for /etc/init.d/bind9 ...
/etc/rc0.d/K20bind9 -> ../init.d/bind9
/etc/rc1.d/K20bind9 -> ../init.d/bind9
/etc/rc6.d/K20bind9 -> ../init.d/bind9
/etc/rc2.d/S20bind9 -> ../init.d/bind9
/etc/rc3.d/S20bind9 -> ../init.d/bind9
/etc/rc4.d/S20bind9 -> ../init.d/bind9
/etc/rc5.d/S20bind9 -> ../init.d/bind9
dns:~# /etc/init.d/bind9 start
Starting domain name service...: bind9.
На этом настройка кэширующего DNS завершена. Все запросы, которые попадают в кэш DNS сервера он хранит в оперативной памяти компьютера и при перезапуске демона эти данные обнуляются. Для проверки работы кэша можно выполнить команду nslookup mail.ru example.com., если в ответе содержится строка Non-authoritative answer, то адрес пришел из кэша, а так же если выполнить dig www.ru. (или другой домен, которого еще нет в кэше) и через некоторое время повторить команду, то время ответа должно быть гораздо меньше.


Простейший замок может быть реализован на основе класса Lock модуля threading. Замок имеет два состояния: он может быть или открыт, или заперт. В последнем случае им владеет некоторый поток. Объект класса Lock имеет следующие методы:
Давайте рассмотрим другие варианты сервера.


acquire([blocking=True]) Делает запрос на запирание замка. Если параметр blocking не указан или является истиной, то поток будет ожидать освобождения замка. Если параметр не был задан, метод не возвратит значения. Если blocking был задан и истинен, метод возвратит True (после успешного овладения замком). Если блокировка не требуется (то есть задан blocking=False ), метод вернет True, если замок не был заперт и им успешно овладел данный поток. В противном случае будет возвращено False.
Главный (master) сервер зоны
release() Запрос на отпирание замка.
locked() Возвращает текущее состояние замка ( True - заперт, False - открыт). Следует иметь в виду, что даже если состояние замка только что проверено, это не означает, что он сохранит это состояние до следующей команды.
Имеется еще один вариант замка - threading.RLock, который отличается от threading.Lock тем, что некоторый поток может запрашивать его запирание много раз. Отпирание такого замка должно происходить столько же раз, сколько было запираний. Это может быть полезно, например, внутри рекурсивных функций.


Основной конфиг содержит следующие настройки:


=== Когда нужны замки? ===
dns:~# cat /etc/bind/named.conf
acl "lan" {
          192.168.1.1/24;
          127.0.0.1;
};


options {
          directory "/var/cache/bind";
          allow-query { any; };      // отвечать на зпросы со всех интерфейсов
          recursion no;              // запретить рекурсивные запросы
          auth-nxdomain no;          // для совместимости RFC1035
          listen-on-v6 { none; };    // IPv6 нам не нужен
          version "unknown";          // не отображать версию DNS сервера при ответах


Замки позволяют ограничивать вход в некоторую область программы одним потоком. Замки могут потребоваться для обеспечения целостности структуры данных. Например, если для корректной работы программы требуется добавление определенного элемента сразу в несколько списков или словарей, такие операции в многопоточном приложении следует обставить замками. Вокруг атомарных операций над встроенными типами (операций, которые не вызывают исполнение какого-то другого кода на Python) замки ставить необязательно. Например, метод append() (встроенного) списка является атомарной операцией, а тот же метод, реализованный пользовательским классом, может требовать блокировок. В случае сомнений, конечно, лучше перестраховаться и поставить замки, однако следует минимизировать общее время действия замка, так как замок останавливает другие потоки, пытающиеся попасть в ту же область программы. Отсутствие замка в критической части программы, работающей над общими для двух и более потоков ресурсами, может привести к случайным, трудноуловимым ошибкам.
          /*
          *  Раскомментируйте строки ниже, если
          *  хотите разрешить рекрусивные запросы
          *  из локальной сети.
          *  (так же, необходимо закомментировать
          *  recursion no; )
          */
          # forwarders {                // указываем DNS сервера для пересылки
          #        83.239.0.202;        // предоставленные провайдером
          #        213.132.67.110;      // ибо до них ближе чем до корневых
          # };


          # allow-recursion { lan; };    // рекурсивные запросы тоже только из локальной


== Тупиковая ситуация (deadlock) ==
};


// описание настроек корневых серверов
zone "." {
          type hint;
          file "db.root";
};


Замки применяются для управления доступом к ресурсу, который нельзя использовать совместно. В программе таких ресурсов может быть несколько. При работе с замками важно хорошо продумать, не зайдет ли выполнение программы в тупик (deadlock) из-за того, что двум потокам потребуются одни и те же ресурсы, но ни тот, ни другой не смогут их получить, так как они уже получили замки. Такая ситуация проиллюстрирована в следующем примере:
// нижеописанные зоны определяют сервер авторитетным для петлевых
// интерфейсов, а так же для броадкаст-зон (согласно RFC 1912)


import threading, time
zone "localhost" {
                resource = {'A': threading.Lock(), 'B': threading.Lock()}
          type master;
                def proc(n, rs):
          file "localhost";
                for r in rs:
};
                  print "Процесс %s запрашивает ресурс %s" % (n, r)
                  resource[r].acquire()
                  print "Процесс %s получил ресурс %s" % (n, r)
                  time.sleep(1)
                print "Процесс %s выполняется" % n
                for r in rs:
                  resource[r].release()
                print "Процесс %s закончил выполнение" % n
                p1 = threading.Thread(target=proc, name="t1", args=["1", "AB"])
                p2 = threading.Thread(target=proc, name="t2", args=["2", "BA"])
                p1.start()
                p2.start()
                p1.join()
                p2.join()


В этом примере два потока (t1 и t2) запрашивают замки к одним и тем же ресурсам (A и B), но в разном порядке, отчего получается, что ни у того, ни у другого не хватает ресурсов для дальнейшей работы, и они оба безнадежно повисают, ожидая освобождения нужного ресурса. Благодаря операторам print можно увидеть последовательность событий:
zone "127.in-addr.arpa" {
          type master;
          file "127.in-addr.arpa";
};


Процесс 1 запрашивает ресурс A
zone "0.in-addr.arpa" {
                Процесс 1 получил ресурс A
          type master;
                Процесс 2 запрашивает ресурс B
          file "0.in-addr.arpa";
                Процесс 2 получил ресурс B
};
                Процесс 1 запрашивает ресурс B
                Процесс 2 запрашивает ресурс A


Существуют методики, позволяющие избежать подобных тупиков, однако их рассмотрение не входит в рамки данной лекции. Можно посоветовать следующие приемы:
zone "255.in-addr.arpa" {
*построить логику приложения так, чтобы никогда не запрашивать замки к двум ресурсам сразу. Возможно, придется определить составной ресурс. В частности, к данному примеру можно было бы определить замок "AB" для указания эксклюзивного доступа к ресурсам A и B.
          type master;
*строго упорядочить все ресурсы (например, по цене) и всегда запрашивать их в определенном порядке (скажем, начиная с более дорогих ресурсов). При этом перед заказом некоторого ресурса поток должен отказаться от заблокированных им более дешевых ресурсов.
          file "255.in-addr.arpa";
};


// описание основной зоны
zone "example.com" {
          type master;
          file "example.com";
          allow-transfer { 10.0.0.191; };
};


== Семафоры ==
//описание обратных зон
zone "0.0.10.in-addr.arpa" {
          type master;
          file "0.0.10.in-addr.arpa";
          allow-transfer { 10.0.0.191; };
};


zone "1.168.192.in-addr.arpa" {
          type master;
          file "1.168.192.in-addr.arpa";
#        allow-transfer { 10.0.0.191; };  // зона описывает локальную сеть поэтому ее не передаем
};


Семафоры (их иногда называют семафорами Дийкстры (Dijkstra) по имени их изобретателя) являются более общим механизмом синхронизации потоков, нежели замки. Семафоры могут допустить в критическую область программы сразу несколько потоков. Семафор имеет счетчик запросов, уменьшающийся с каждым вызовом метода acquire() и увеличивающийся при каждом вызове release(). Счетчик не может стать меньше нуля, поэтому в таком состоянии потокам приходится ждать, как и в случае с замками, пока значение счетчика не увеличится.
// настройки логирования
logging {
          channel "misc" {
                    file "/var/log/bind/misc.log" versions 4 size 4m;
                    print-time yes;
                    print-severity yes;
                    print-category yes;
          };


Конструктор класса threading.Semaphore принимает в качестве (необязательного) аргумента начальное состояние счетчика (по умолчанию оно равно 1, что соответствует замку класса Lock ). Методы acquire() и release() действуют аналогично описанным выше одноименным методам у замков.
          channel "query" {
                    file "/var/log/bind/query.log" versions 4 size 4m;
                    print-time yes;
                    print-severity no;
                    print-category no;
          };


Семафор может применяться для охраны ограниченного ресурса. Например, с его помощью можно вести пул соединений с базой данных. Пример такого использования семафора (заимствован из документации к Python) дан ниже:
          category default {
                    "misc";
          };


from threading import BoundedSemaphore
          category queries {
            maxconnections = 5
                    "query";
            # Подготовка семафора
          };
            pool_sema = BoundedSemaphore(value=maxconnections)
};
Давайте кратко разберем конфигурационный файл и настройки master сервера: мы настраиваем мастер сервер для зоны example.com. . Согласно конфига, наш BIND имеет рабочий каталог /var/cache/bind, сервер отвечает на запросы со всех интерфейсов (allow-query {any ;};), рекурсивные запросы обрабатывает как итеративные (recursion no), является мастер-сервером для зоны example.com и локальных служебных зон (type master). При этом, если необходимо разрешить кэширование (то есть рекурсивные запросы) для локальной сети, то необходимо раскомментировать параметры forwarders и allow-recursion и закомментировать recursion no;.


            # Внутри потока:
Так же, для примера, я привел возможности BIND логировать все происходящее при работе сервера (можно для этой цели использовать syslog). В разделе logging задаются 2 параметра channel (можно и больше двух - на ваше усмотрение), эти параметры дословно можно назвать "канал" записи. Каждый канал определяет имя канала и настройки параметров записи (что записывать, а что - нет и куда писать). Директива category задает какую категорию сообщений в какой канал отправлять. Исходя из этого, мы имеем: запись стандартной информации в канал misc, а приходящие запросы посылаются в канал query. При этом, если файлы журнала достигают 4Мб (size 4m), он переименовывается добавлением к имени .1 и начинается запись в новый журнал, числа в конце других журналов увеличиваются. Журналы с номером, более указанного в version (в нашем случае 4) удаляются (Управлять ротацией логов можно так же с помощью logrotate). Параметры print* определяют заносить ли в журнал время появления, важность и категорию информации. Более подробно про настройки раздела logging можно почитать в man (5) named.conf.


            pool_sema.acquire()
Отдельно хочется описать параметр  allow-transfer { 10.0.0.191; };. Данный параметр описывает серверы, которым разрешено скачивать копию зоны - т.н. slave серверА. В следующем примере мы разберем настройку slave DNS.
            conn = connectdb()
            # ... использование соединения ...
            conn.close()
            pool_sema.release()


Таким образом, применяется не более пяти соединений с базой данных. В примере использован класс threading.BoundedSemaphore. Экземпляры этого класса отличаются от экземпляров класса threading.Semaphore тем, что не дают сделать release() больше, чем сделан acquire().
Для корректной работы логирования необходимо создать соответствующий каталог и присвоить необходимые права:


dns:~# mkdir /var/log/bind/
dns:~# chmod 744 /var/log/bind/
dns:~# ps aux | grep named
bind      4298  0.0  3.4  46792 13272 ?        Ssl  Jul05  0:00 /usr/sbin/named -u bind
root      4815  0.0  0.1  3304  772 pts/4    S+  18:19  0:00 grep named
dns:~# chown bind /var/log/bind/
dns:~# ls -ld /var/log/bind/
drwxr--r-- 2 bind root 4096 Июл  6 18:18 /var/log/bind/
Давайте далее рассмотрим наш файл описания зоны example.com.:


== События ==
dns:~# cat /var/cache/bind/example.com
$TTL 3D
@      IN      SOA    ns.example.com. root.example.com. (
                                        2011070601      ; serial
                                        8H              ; refresh
                                        2H              ; retry
                                        2W              ; expire
                                        1D)            ; minimum


@      IN      NS      ns.example.com.
@      IN      NS      ns2.example.com.
@      IN      A      10.0.0.152
@      IN      MX      5 mx.example.com.
ns      IN      A      10.0.0.152
ns2    IN      A      10.0.0.191
mx      IN      A      10.0.0.152
www    IN      CNAME  @
а так же в домене in-addr.arpa.


Еще одним способом коммуникации между объектами являются события. Экземпляры класса threading.Event могут быть использованы для передачи информации о наступлении некоторого события от одного потока одному или нескольким другим потокам. Объекты-события имеют внутренний флаг, который может находиться в установленном или сброшенном состоянии. При своем создании флаг события находится в сброшенном состоянии. Если флаг в установленном состоянии, ожидания не происходит: поток, вызвавший метод wait() для ожидания события, просто продолжает свою работу. Ниже приведены методы экземпляров класса threading.Event:
dns:~# cat /var/cache/bind/0.0.10.in-addr.arpa
$TTL 3600
@      IN      SOA    ns.examle.com.  root.example.com. (
            2007042001 ; Serial
            3600      ; Refresh
            900        ; Retry
            3600000    ; Expire
            3600 )     ; Minimum
        IN      NS      ns.examle.com.
        IN      NS      ns2.example.com.
152    IN      PTR    examle.com.
191    IN      PTR    ns.example.com.
*      IN      PTR    examle.com.


*set() Устанавливает внутренний флаг, сигнализирующий о наступлении события. Все ждущие данного события потоки выходят из состояния ожидания.
dns:~# cat /var/cache/bind/1.168.192.in-addr.arpa
*clear() Сбрасывает флаг. Все события, которые вызывают метод wait() этого объекта-события, будут находиться в состоянии ожидания до тех пор, пока флаг сброшен, или по истечении заданного таймаута.
$TTL 3600
*isSet() Возвращает состояние флага.
@      IN      SOA    ns.examle.com.  root.example.com. (
*wait([timeout]) Переводит поток в состояние ожидания, если флаг сброшен, и сразу возвращается, если флаг установлен. Аргумент timeout задает таймаут в секундах, по истечении которого ожидание прекращается, даже если событие не наступило.
            2007042001 ; Serial
            3600      ; Refresh
            900        ; Retry
            3600000    ; Expire
            3600 )     ; Minimum
        IN      NS      ns.examle.com.
        IN      NS      ns2.example.com.
*       IN      PTR    examle.com.
Наша сеть небольшая, предполагается, что в сети совсем мало машин. Все сервисы сети размещены на одном хосте example.com., поэтому и master DNS (ns.example.com.) и почтовый сервер (mx.example.com.) указывает на одну машину (10.0.0.152).


Вторичный (secondary, slave) авторитетный сервер зоны


Основная функция slave сервера - автоматическая синхронизация описания зоны с master сервером. Данная задача регламентируется документом RFC 1034 в разделе 4.3.5. Согласно данному документу обмен данными между серверами рекомендовано производить по протоколу TCP, посредством запроса AXFR. По этому запросу за одно TCP соединение должна передаваться вся зона целиком (RFC 1035).


== Условия ==
Так же, slave DNS-сервер делит нагрузку с master сервером или принимает на себя всю нагрузку в случае аварии па первом сервере.


Прежде чем приступить к настройке slave DNS сервера, необходимо проверить возможность получения зоны вручную со вторичного сервера с помощью следующей команды:


Более сложным механизмом коммуникации между потоками является механизм условий. Условия представляются в виде экземпляров класса threading.Condition и, подобно только что рассмотренным событиям, оповещают потоки об изменении некоторого состояния. Конструктор класса threading.Condition принимает необязательный параметр, задающий замок класса threading.Lock или threading.RLock. По умолчанию создается новый экземпляр замка класса threading.RLock. Методы объекта-условия описаны ниже:
root@debian:~# dig @10.0.0.152 example.com. axfr


*acquire(...) Запрашивает замок. Фактически вызывается одноименный метод принадлежащего объекту-условию объекта-замка.
; <<>> DiG 9.7.3 <<>> @10.0.0.152 example.com. axfr
*release() Снимает замок.
; (1 server found)
*wait([timeout]) Переводит поток в режим ожидания. Этот метод может быть вызван только в том случае, если вызывающий его поток получил замок. Метод снимает замок и блокирует поток до появления объявлений, то есть вызовов методов notify() и notifyAll() другими потоками. Необязательный аргумент timeout задает таймаут ожидания в секундах. При выходе из ожидания поток снова запрашивает замок и возвращается из метода wait().
;; global options: +cmd
*notify() Выводит из режима ожидания один из потоков, ожидающих данные условия. Метод можно вызвать, только овладев замком, ассоциированным с условием. Документация предупреждает, что в будущих реализациях модуля из целей оптимизации этот метод будет прерывать ожидание сразу нескольких потоков. Сам по себе метод notify() не приводит к продолжению выполнения ожидавших условия потоков, так как этому препятствует занятый замок. Потоки получают управление только после снятия замка потоком, вызвавшим метод notify().
example.com.            259200  IN      SOA    ns.example.com. root.example.com. 2011070801 28800 7200 1209600 86400
*notifyAll() Этот метод аналогичен методу notify(), но прерывает ожидание всех ждущих выполнения условия потоков.
example.com.            259200  IN      NS      ns.example.com.
example.com.            259200  IN      NS      ns2.example.com.
example.com.            259200  IN      A      10.0.0.152
example.com.            259200  IN      MX      5 mx.example.com.
mx.example.com.        259200  IN      A      10.0.0.152
ns.example.com.        259200  IN      A      10.0.0.152
ns2.example.com.        259200  IN      A      10.0.0.191
www.example.com.       259200  IN      CNAME  example.com.
example.com.            259200  IN      SOA    ns.example.com. root.example.com. 2011070801 28800 7200 1209600 86400
;; Query time: 14 msec
;; SERVER: 10.0.0.152#53(10.0.0.152)
;; WHEN: Fri Jul  8 15:33:54 2011
;; XFR size: 11 records (messages 1, bytes 258)
Получение зоны прошло успешно. Далее, для настройки подчиненного сервера, алгоритм следующий:


В следующем примере условия используются для оповещения потоков о прибытии новой порции данных (организуется связь производитель - потребитель, producer - consumer):
Скопировать конфигурационный файл named.conf с master сервера;
Заменить параметр type master на type slave в тех зонах, для которых он будет вторичным;
Параметр  allow-transfer { 10.0.0.191; }; заменить на masters { 10.0.0.152;}; в тех зонах, для которых он будет вторичным;
Удалить зоны, которые не будет обслуживать текущий сервер, в том числе и корневую, если slave не будет отвечать на рекурсивные запросы;
Создать каталоги для логов, как в предыдущем примере.
Итого, мы получаем конфиг slave сервера:


import threading
root@debian:~# cat /etc/bind/named.conf
            cv = threading.Condition()
options {
            class Item:
          directory "/var/cache/bind";
              """Класс-контейнер для элементов, которые будут потребляться
          allow-query { any; };      // отвечать на запросы со всех интерфейсов
              в потоках"""
          recursion no;              // запретить рекурсивные запросы
              def __init__(self):
          auth-nxdomain no;          // для совместимости RFC1035
                self._items = []
          listen-on-v6 { none; };    // IPv6 нам не нужен
              def is_available(self):
          version "unknown";        // не отображать версию DNS сервера при ответах
                return len(self._items) > 0
};
              def get(self):
                return self._items.pop()
              def make(self, i):
                self._items.append(i)
            item = Item()
            def consume():
              """Потребление очередного элемента (с ожиданием его появления)"""
              cv.acquire()
              while not item.is_available():
                cv.wait()
              it = item.get()
              cv.release()
              return it
            def consumer():
              while True:
                print consume()
            def produce(i):
              """Занесение нового элемента в контейнер и оповещение потоков"""
              cv.acquire()
              item.make(i)
              cv.notify()
              cv.release()
            p1 = threading.Thread(target=consumer, name="t1")
            p1.setDaemon(True)
            p2 = threading.Thread(target=consumer, name="t2")
            p2.setDaemon(True)
            p1.start()
            p2.start()
            produce("ITEM1")
            produce("ITEM2")
            produce("ITEM3")
            produce("ITEM4")
            p1.join()
            p2.join()


В этом примере условие cv отражает наличие необработанных элементов в контейнере item. Функция produce() "производит" элементы, а consume(), работающая внутри потоков, "потребляет". Стоит отметить, что в приведенном виде программа никогда не закончится, так как имеет бесконечный цикл в потоках, а в главном потоке - ожидание завершения этих потоков. Еще одна особенность - признак демона, установленный с помощью метода setDaemon() объекта-потока до его старта.
// нижеописанные зоны определяют сервер авторитетным для петлевых
// интерфейсов, а так же для броадкаст-зон (согласно RFC 1912)


zone "localhost" {
          type master;
          file "localhost";
};


== Очередь ==
zone "127.in-addr.arpa" {
          type master;
          file "127.in-addr.arpa";
};


Процесс, показанный в предыдущем примере, имеет значение, достойное отдельного модуля. Такой модуль в стандартной библиотеке языка Python есть, и он называется Queue.
zone "0.in-addr.arpa" {
          type master;
          file "0.in-addr.arpa";
};


Помимо исключений - Queue.Full (очередь переполнена) и Queue.Empty (очередь пуста) - модуль определяет класс Queue, заведующий собственно очередью.
zone "255.in-addr.arpa" {
          type master;
          file "255.in-addr.arpa";
};


Собственно, здесь можно привести аналог примера выше, но уже с использованием класса Queue.Queue:
// описание основной зоны
zone "example.com" {
          type slave;
          file "example.com";
          masters { 10.0.0.152; };
};


import threading, Queue
//описание обратной зоны
            item = Queue.Queue()
zone "0.0.10.in-addr.arpa" {
            def consume():
          type slave;
              """Потребление очередного элемента (с ожиданием его появления)"""
          file "0.0.10.in-addr.arpa";
              return item.get()
          masters { 10.0.0.152; };
            def consumer():
};
              while True:
                print consume()
            def produce(i):
              """Занесение нового элемента в контейнер и оповещение потоков"""
              item.put(i)
            p1 = threading.Thread(target=consumer, name="t1")
            p1.setDaemon(True)
            p2 = threading.Thread(target=consumer, name="t2")
            p2.setDaemon(True)
            p1.start()
            p2.start()
            produce("ITEM1")
            produce("ITEM2")
            produce("ITEM3")
            produce("ITEM4")
            p1.join()
            p2.join()


Следует отметить, что все блокировки спрятаны в реализации очереди, поэтому в коде они явным образом не присутствуют.
// настройки логирования
logging {
          channel "misc" {
                    file "/var/log/bind/misc.log" versions 4 size 4m;
                    print-time YES;
                    print-severity YES;
                    print-category YES;
          };


          channel "query" {
                    file "/var/log/bind/query.log" versions 4 size 4m;
                    print-time YES;
                    print-severity NO;
                    print-category NO;
          };


== Модуль thread ==
          category default {
                    "misc";
          };


          category queries {
                    "query";
          };
};
после перезапуска наш slave сервер благополучно скопирует необходимую ему информацию с главного сервера, о чем будет говорить наличие файлов в  каталоге:


По сравнению с модулем threading, модуль thread предоставляет низкоуровневый доступ к потокам. Многие функции модуля threading, который рассматривался до этого, реализованы на базе модуля thread. Здесь стоит сделать некоторые замечания по применению потоков вообще. Документация по Python предупреждает, что использование потоков имеет особенности:
root@debian:~# ls -la /var/cache/bind/
итого 28
drwxrwxr-x  2 root bind 4096 Июл  8 18:47 .
drwxr-xr-x 10 root root 4096 Июл  8 15:17 ..
-rw-r--r--  1 bind bind  416 Июл  8 18:32 0.0.10.in-addr.arpa
......
-rw-r--r--  1 bind bind  455 Июл  8 18:32 example.com
........
В принципе,/stroallow-transfer {pngp slave сервер может не хранить копию зоны у себя в файловой системе. Эта копия нужна только в момент старта DNS. Наличие копии зоны в файловой системе может избавить от сбоя при недоступности master сервера во время запуска slave DNS. Если не указать опцию file в разделе zone, то копия не создается.


Исключение KeyboardInterrupt (прерывание от клавиатуры) может быть получено любым из потоков, если в поставке Python нет модуля signal (для обработки сигналов).
Настройка netfilter (iptables) для DNS BIND
Не все встроенные функции, блокированные ожиданием ввода, позволяют другим потокам работать. Правда, основные функции вроде time.sleep(), select.select(), метод read() файловых объектов не блокируют другие потоки.
Невозможно прервать метод acquire(), так как исключение KeyboardInterrupt возбуждается только после возврата из этого метода.
Нежелательно, чтобы главный поток завершался раньше других потоков, так как не будут выполнены необходимые деструкторы и даже части finally в операторах try-finally. Это связано с тем, что почти все операционные системы завершают приложение, у которого завершился главный поток.
Визуализация работы потоков


Следующий пример иллюстрирует параллельность выполнения потоков, используя возможности библиотеки графических примитивов Tkinter (она входит в стандартную поставку Python). Несколько потоков наперегонки увеличивают размеры прямоугольника некоторого цвета. Цветом победившего потока окрашивается кнопка Go:
Собственно, настроив работу сервера, неплохо было бы его защитить. Мы знаем, что сервер работает на 53/udp порту. Почитав статью о том, что такое netfilter и правила iptables и ознакомившись с практическими примерами iptables, можно создать правила фильтрации сетевого трафика:


import threading, time, sys
dns ~ # iptables-save
            from Tkinter import Tk, Canvas, Button, LEFT, RIGHT, NORMAL, DISABLED
# типовые правила iptables для DNS
            global champion
*filter
            # Задается дистанция, цвет полосок и другие параметры
:INPUT DROP [7511:662704]
            distance = 300
:FORWARD DROP [0:0]
            colors = ["Red","Orange","Yellow","Green","Blue","DarkBlue","Violet"]
:OUTPUT DROP [0:0]
            nrunners = len(colors)      # количество дополнительных потоков
-A INPUT -i lo -j ACCEPT
            positions = [0] * nrunners  # список текущих позиций
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
            h, h2 = 20, 10              # параметры высоты полосок
-A INPUT -m conntrack --ctstate INVALID -j DROP
            def run(n):
# разрешить доступ локальной сети к DNS серверу:
              """Программа бега n-го участника (потока)"""
-A INPUT -s 192.168.1.1/24 -d 192.168.1.1/32 -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
              global champion
-A OUTPUT -o lo -j ACCEPT
              while 1:
-A OUTPUT -p icmp -j ACCEPT
                for i in range(10000):          # интенсивные вычисления
-A OUTPUT -p udp -m udp --sport 32768:61000 -j ACCEPT
                  pass
-A OUTPUT -p tcp -m tcp --sport 32768:61000 -j ACCEPT
                graph_lock.acquire()
-A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
                positions[n] += 1                # передвижение на шаг
# разрешить доступ DNS серверу совершать исходящие запросы
                if positions[n] == distance:     # если уже финиш
-A OUTPUT -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
                  if champion is None:          # и чемпион еще не определен,
COMMIT
                    champion = colors[n]         # назначается чемпион
Это типовой пример! Для задания правил iptables под Ваши задачи и конфигурацию сети, необходимо понимать принцип работы netfilter в Linux, почитав вышеуказанные статьи.
                  graph_lock.release()
                  break
                graph_lock.release()
            def ready_steady_go():
              """Инициализация начальных позиций и запуск потоков"""
              graph_lock.acquire()
              for i in range(nrunners):
                positions[i] = 0
                threading.Thread(target=run, args=[i,]).start()
              graph_lock.release()
            def update_positions():
              """Обновление позиций"""
              graph_lock.acquire()
              for n in range(nrunners):
                c.coords(rects[n], 0, n*h, positions[n], n*h+h2)
              tk.update_idletasks()  # прорисовка изменений
              graph_lock.release()
            def quit():
              """Выход из программы"""
              tk.quit()
              sys.exit(0)
            # Прорисовка окна, основы для прямоугольников и самих прямоугольников,
            # кнопок для пуска и выхода
            tk = Tk()
            tk.title("Соревнование потоков")
            c = Canvas(tk, width=distance, height=nrunners*h, bg="White")
            c.pack()
            rects = [c.create_rectangle(0, i*h, 0, i*h+h2, fill=colors[i])
                    for i in range(nrunners)]
            go_b = Button(text="Go", command=tk.quit)
            go_b.pack(side=LEFT)
            quit_b = Button(text="Quit", command=quit)
            quit_b.pack(side=RIGHT)
            # Замок, регулирующий доступ к функции пакета Tk
            graph_lock = threading.Lock()
            # Цикл проведения соревнований
            while 1:
              go_b.config(state=NORMAL), quit_b.config(state=NORMAL)
              tk.mainloop()            # Ожидание нажатия клавиш
              champion = None
              ready_steady_go()
              go_b.config(state=DISABLED), quit_b.config(state=DISABLED)
              # Главный поток ждет финиша всех участников
              while sum(positions) < distance*nrunners:
                update_positions()
              update_positions()
              go_b.config(bg=champion)    # Кнопка окрашивается в цвет победителя
              tk.update_idletasks()


Примечание:
Устранение неполадок


Эта программа использует некоторые возможности языка Python 2.3 (встроенную функцию sum() и списковые включения), поэтому для ее выполнения нужен Python версии не меньше 2.3.
Основным источником для выявления проблем с DNS является системный лог. Вот пример ошибок при запуске, когда я ошибся с путем к файлу зоны коревых серверов:


Jul  5 18:12:43 dns-server named[4224]: starting BIND 9.7.3 -u bind
Jul  5 18:12:43 dns-server named[4224]: built with '--prefix=/usr' '--mandir=/usr/share/man' '--infodir=/usr/share/info' '--sysconfdir=/etc/bind' '--localstatedir=/var' '--enable-threads' '--enable-largefile' '--with-libtool' '--enable-shared' '--enable-static' '--with-openssl=/usr' '--with-gssapi=/usr' '--with-gnu-ld' '--with-dlz-postgres=no' '--with-dlz-mysql=no' '--with-dlz-bdb=yes' '--with-dlz-filesystem=yes' '--with-dlz-ldap=yes' '--with-dlz-stub=yes' '--with-geoip=/usr' '--enable-ipv6' 'CFLAGS=-fno-strict-aliasing -DDIG_SIGCHASE -O2' 'LDFLAGS=' 'CPPFLAGS='
Jul  5 18:12:43 dns-server named[4224]: adjusted limit on open files from 1024 to 1048576
Jul  5 18:12:43 dns-server named[4224]: found 1 CPU, using 1 worker thread
Jul  5 18:12:43 dns-server named[4224]: using up to 4096 sockets
Jul  5 18:12:43 dns-server named[4224]: loading configuration from '/etc/bind/named.conf'
Jul  5 18:12:43 dns-server named[4224]: reading built-in trusted keys from file '/etc/bind/bind.keys'
Jul  5 18:12:43 dns-server named[4224]: using default UDP/IPv4 port range: [1024, 65535]
Jul  5 18:12:43 dns-server named[4224]: using default UDP/IPv6 port range: [1024, 65535]
Jul  5 18:12:43 dns-server named[4224]: listening on IPv4 interface lo, 127.0.0.1#53
Jul  5 18:12:43 dns-server named[4224]: listening on IPv4 interface eth1, 192.168.1.1#53
Jul  5 18:12:43 dns-server named[4224]: generating session key for dynamic DNS
Jul  5 18:12:43 dns-server named[4224]: could not configure root hints from '/etc/bind/db.root': file not found
Jul  5 18:12:43 dns-server named[4224]: loading configuration: file not found            # файл не найден
Jul  5 18:12:43 dns-server named[4224]: exiting (due to fatal error)
Jul  5 18:15:05 dns-server named[4298]: starting BIND 9.7.3 -u bind
Jul  5 18:15:05 dns-server named[4298]: built with '--prefix=/usr' '--mandir=/usr/share/man' '--infodir=/usr/share/info' '--sysconfdir=/etc/bind' '--localstatedir=/var' '--enable-threads' '--enable-largefile' '--with-libtool' '--enable-shared' '--enable-static' '--with-openssl=/usr' '--with-gssapi=/usr' '--with-gnu-ld' '--with-dlz-postgres=no' '--with-dlz-mysql=no' '--with-dlz-bdb=yes' '--with-dlz-filesystem=yes' '--with-dlz-ldap=yes' '--with-dlz-stub=yes' '--with-geoip=/usr' '--enable-ipv6' 'CFLAGS=-fno-strict-aliasing -DDIG_SIGCHASE -O2' 'LDFLAGS=' 'CPPFLAGS='
Jul  5 18:15:05 dns-server named[4298]: adjusted limit on open files from 1024 to 1048576
Jul  5 18:15:05 dns-server named[4298]: found 1 CPU, using 1 worker thread
Jul  5 18:15:05 dns-server named[4298]: using up to 4096 sockets
Jul  5 18:15:05 dns-server named[4298]: loading configuration from '/etc/bind/named.conf'
Jul  5 18:15:05 dns-server named[4298]: using default UDP/IPv4 port range: [1024, 65535]
Jul  5 18:15:05 dns-server named[4298]: using default UDP/IPv6 port range: [1024, 65535]
Jul  5 18:15:05 dns-server named[4298]: listening on IPv4 interface lo, 127.0.0.1#53
Jul  5 18:15:05 dns-server named[4298]: listening on IPv4 interface eth1, 192.168.1.1#53
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 254.169.IN-ADDR.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 2.0.192.IN-ADDR.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 100.51.198.IN-ADDR.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 113.0.203.IN-ADDR.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 255.255.255.255.IN-ADDR.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: D.F.IP6.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 8.E.F.IP6.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 9.E.F.IP6.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: A.E.F.IP6.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: B.E.F.IP6.ARPA
Jul  5 18:15:05 dns-server named[4298]: automatic empty zone: 8.B.D.0.1.0.0.2.IP6.ARPA
Jul  5 18:15:05 dns-server named[4298]: zone 0.in-addr.arpa/IN: loaded serial 1
Jul  5 18:15:05 dns-server named[4298]: zone 127.in-addr.arpa/IN: loaded serial 1
Jul  5 18:15:05 dns-server named[4298]: zone 255.in-addr.arpa/IN: loaded serial 1
Jul  5 18:15:05 dns-server named[4298]: zone localhost/IN: loaded serial 2
Jul  5 18:15:05 dns-server named[4298]: running                                  # запуск прошел удачно
Отличным инструментом для диагностики являются команды диагностики DNS.


== Заключение ==
* [http://www.k-max.name/linux/howto-dns-server-bind/ HOWTO DNS сервер BIND (практика)]
 
 
Навыки параллельного программирования необходимы любому профессиональному программисту. Одним из вариантов организации (псевдо) параллельного программирования является многопоточное программирование (другой вариант, более свойственный Unix-системам - многопроцессное программирование - здесь не рассматривается). В обычной (однопоточной) программе действует всего один поток управления, а в многопоточной одновременно могут работать несколько потоков.
 
Параллельное программирование требует тщательной отработки взаимодействия между потоками управления. Некоторые участки кода необходимо ограждать от одновременного использования двумя различными потоками, дабы не нарушить целостность изменяемых структур данных или логику работы с внешними ресурсами. Для ограждения участков кода используются замки и семафоры.
 
Стандартная библиотека Python предоставляет довольно неплохой набор возможностей для многопоточного программирования в модулях threading и thread, а также некоторые полезные вспомогательные модули (например, Queue ).

Версия от 16:26, 16 октября 2016

Исходные данные

Для корректной работы DNS нем необходимо иметь настроенную сеть. DNS в текущей статье будет настроен на дистрибутиве Debian, особенности других дистрибутивов тоже будут отмечены. Конфиг сети стенда следующий:

dns:~# cat /etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
 address 10.0.0.152
 netmask 255.255.255.0
 gateway 10.0.0.254

auto eth1
iface eth1 inet static
 address 192.168.1.1
 netmask 255.255.255.0

где 10.0.0.152/24 - внешний интерфейс (подсеть, выделенная провайдером), 192.168.1.1/24 - внутренний (Локальная сеть). Настраиваемая зона будет иметь имя example.com. В примере со slave сервером, вторичный сервер будет расположен на IP 10.0.0.191.

Установка BIND9

Для работы DNS сервера необходимо установить пакет bind9 (в некоторых дистрибутивах - bind). Как отмечено на схеме - основным конфигурационным файлом BIND является файл named.conf (данный файл может быть размещен в каталоге /etc, иногда в /etc/bind ).

Параметры (синтаксис) named.conf

Синтаксис файла named.conf придерживается следующих правил:

IP-адреса - список IP должен быть разделен символом ";" , возможно указывать подсеть в формате 192.168.1.1/24 или 192.168.1.1/255.255.255.0, (для исключения IP перед ним нужно поставить знак !), возможно указывать имена "any", "none", "localhost" в двойных кавычках.

Комментарии - строки начинающиеся на #, // и заключенные в /* и */ считаются комментариями.

В файлах описания зон - символ @ является "переменной" хранящей имя зоны, указанной в конфигурационном файле named.conf или в директиве @ $ORIGIN текущего описания зоны.

Каждая завершенная строка параметров должна завершаться символом ; .

Раздел Acl Acl (access control list) - позволяет задать именованный список сетей. Формат раздела: acl "имя_сети" {ip; ip; ip; };

Раздел Options Раздел Options задает глобальные параметры конфигурационного файла, управляющие всеми зонами. Данный раздел имеет формат: options {операторы_раздела_Options};. Options может быть "вложен" в раздел Zone, при этом он переопределяет глобальные параметры. Часто используемые операторы options:

allow-query {список_ip} - Разрешает ответы на запросы только из список_ip. При отсутствии - сервер отвечает на все запросы.
allow-recursion {список_ip} - На запросы из список_ip будут выполняться рекурсивные запросы. Для остальных - итеративные. Если  не задан параметр, то сервер выполняет рекурсивные запросы для всех сетей.
allow-transfer {список_ip} - Указывает список серверов, которым разрешено брать зону с сервера (в основном тут указывают slave сервера)
directory /path/to/work/dir - указывает абсолютный путь к рабочему каталогу сервера. Этот оператор допустим только в разделе  options.
forwarders {ip порт, ip порт...} - указывает адреса хостов и если нужно порты, куда переадресовывать запросы (обычно тут указываются DNS провайдеров ISP).
forward ONLY или forward FIRST - параметр first указывает, DNS-серверу пытаться разрешать имена с помощью DNS-серверов, указанных в параметре forwarders, и лишь в случае, если разрешить имя с помощью данных серверов не удалось, то будет осуществлять попытки разрешения имени самостоятельно.
notify YES|NO - YES - уведомлять slave сервера об изменениях в зоне, NO - не уведомлять.
recursion YES|NO - YES - выполнять рекурсивные запросы, если просит клиент, NO - не выполнять (только итеративные запросы). Если ответ найден в кэше, то возвращается из кэша. (может использоваться только в разделе Options)

Раздел Zone Определяет описание зон(ы). Формат раздела: zone {операторы_раздела_zone}; Операторы, которые наиболее часто используются:

allow-update {список_ip} - указывает системы, которым разрешено динамически обновлять данную зону.
file "имя_файла" - указывает путь файла параметров зоны (должен быть расположен в каталоге, определенном в разделе options оператором directory)
masters {список_ip} -указывает список мастер-серверов. (допустим только в подчиненных зонах)
type "тип_зоны" - указывает тип зоны, описываемой в текущем разделе,тип_зоны может принимать следующие значения:
forward - указывает зону переадресации, которая переадресовывает запросы, пришедшие в эту зону.
hint - указывает вспомогательную зону (данный тип содержит информацию о корневых серверах, к которым сервер будет обращаться в случае невозможности найти ответ в кэше)
master - указывает работать в качестве мастер сервера для текущей зоны.
slave - указывает работать в качестве подчиненного сервера для текущей зоны.

Дополнительные параметры конфигурации Значения времени в файлах зон по умолчанию указывается в секундах, если за ними не стоит одна из следующих букв: S - секунды, M - минуты, H- часы, D - дни, W - недели. Соответственно, запись 2h20m5s будет иметь значение 2 часа 20 минут 5 секунд и соответствовать 8405 секунд.

Любое имя хоста/записи, не оканчивающиеся точкой считается неFQDN именем и будет дополнено именем текущей зоны. Например, запись domen в файле зоны examle.com будет развернуто в FQDN-имя domen.examle.com. .

В конфигурационных файлах BIND могут применяться следующие директивы:

$TTL - определяет TTL по-умолчанию для всех записей в текущей зоне.
$ORIGIN - изменяет имя зоны с указанного в файле named.conf. При этом, область действия данной директивы не распространяется  "выше" (то есть если файл включен директивой $INCLUDE, то область действия$ORIGN не распространяется на родительский)
$INCLUDE - включает указанный файл как часть файла зоны.

Для того чтобы локальный резолвер сервера тоже использовал локальный DNS, необходимо привести файл resolv.conf к следующему виду:

dns:~# cat /etc/resolv.conf
nameserver 127.0.0.1

Если в имени ресурсной записи встречается символ "*", то это он означает что вместо него можно подразумевать любую разрешенную последовательность символов. Такую запись называют "wildcard запись". Однако, символ "*" не может быть использован где угодно. Это может быть только первый символ в поле Name текущего домена, отделенный от остальных символом "."

Настройка кэширующего DNS сервера

После установки bind, он полностью готов работать как кэширующий DNS сервер без дополнительной настройки. Единственный недостаток - он обрабатывает запросы на всех интерфейсах, что нам абсолютно не нужно, поэтому мы немного подредактируем настройки сервера.

Для того, чтобы BIND работал в качестве кэширующего сервера, необходимо иметь конфигурационные файлы заполненные необходимой информацией:

named.conf;
описание серверов корневой зоны (зона типа hint);
описание зоны 127.in-addr.arpa.
dns:~# cat /etc/bind/named.conf
acl "lan" {
           192.168.1.1/24;
           127.0.0.1;
};

options {
           directory "/var/cache/bind";

          // If there is a firewall between you and nameservers you want
          // to talk to, you may need to fix the firewall to allow multiple
          // ports to talk.  See http://www.kb.cert.org/vuls/id/800113
          /*
          * Тут сказано, что если используется фаерволл, то необходимо
          *  нашему серверу создать соответствующие правила
          *  то есть открыть доступ по 53 TCP и UDP порту
          */

          forward first;              // задаем пересылку только первого запроса

          forwarders {                // указываем DNS сервера для пересылки
                     83.239.0.202;    // предоставленные провайдером
                     213.132.67.110;  // ибо до них ближе чем до корневых
          };

         listen-on { lan; };        // пусть слушает только нужные интерфейсы
         allow-query { lan; };      // разрешить запросы только из локальной сети
         allow-recursion { lan; };  // рекурсивные запросы тоже только из локальной
         allow-transfer { none; };  // трансфер зон нам не нужен

         version "unknown";         // не отображать версию DNS сервера при ответах

         auth-nxdomain no;    # для совместимости RFC1035
         listen-on-v6 { none; };    //IPv6 нам не нужен
         };

// описание настроек корневых серверов
zone "." {
         type hint;
         file "db.root";
};

// нижеописанные зоны определяют сервер авторитетным для петлевых
// интерфейсов, а так же для броадкаст-зон (согласно RFC 1912)

zone "localhost" {
         type master;
         file "localhost";
};

zone "127.in-addr.arpa" {
         type master;
          file "127.in-addr.arpa";
};

zone "0.in-addr.arpa" {
         type master;
         file "0.in-addr.arpa";
};

zone "255.in-addr.arpa" {
         type master;
         file "255.in-addr.arpa";
};

В данном примере приведен кэширующий DNS сервер, обрабатывающий запросы из списка сетей lan, в которую входит только одна локальная сеть 192.168.1.1/24 и петлевой интерфейс. При необходимости можно включить туда и другие сети. После определения списка сетей в директиве acl, в любом месте конфига можно будет ссылаться на этот список по имени (в нашем примере имя - lan), что, собственно и сделано в разделе options. Большинство параметров я прокомментировал, но отдельного внимания требует раздел, описывающий зону корневых серверов. В параметре file задан относительный путь к файлу описания корневых серверов (путь, относительно рабочего каталога сервера). За обновлениями данного файла необходимо следить, хотя он обновляется довольно редко (откуда брать обновленный файл я писал в теории DNS). Как вы заметили, имеется так же две записи для зоны localhost и две записи обратных зон для бродкаст доменов. Назначение этих зон состоит в том, чтобы избежать трансляции случайных запросов имен соответствующих IP-адресов на серверы, обслуживающие корневую зону.

Чтобы не вносить неразбериху в куче конфигурационных файлов, в статье я привожу примеры на основе единого конфигурационного файла. На самом деле, в последних версиях Debian (и других дистрибутивах Linux), файл named.conf выглядит следующим образом:

root@master:~# cat /etc/bind/named.conf // This is the primary configuration file for the BIND DNS server named. // // Please read /usr/share/doc/bind9/README.Debian.gz for information on the // structure of BIND configuration files in Debian, *BEFORE* you customize // this configuration file. // // If you are just adding zones, please do that in /etc/bind/named.conf.local

include "/etc/bind/named.conf.options"; include "/etc/bind/named.conf.local"; include "/etc/bind/named.conf.default-zones"; То есть основной файл не содержит конфигураций, а включает в себя более узко специализированные файлы, которые отвечают за свои задачи, например named.conf.options - содержит глобальные параметры конфигурации, named.conf.default-zones - содержит описание localhost и broadcast зон, а named.conf.local содержит описания зон, за которые отвечает данный сервер.


Далее, хочу обратить внимание на наличие файлов зон в каталоге, указанном в разделе options в параметре directory с именами, соответствующими параметрам file в разделах, описывающих зоны:

dns:~# ls -l /var/cache/bind/ итого 24 -rw-r--r-- 1 root root 237 Май 28 01:28 0.in-addr.arpa -rw-r--r-- 1 root root 271 Май 28 01:28 127.in-addr.arpa -rw-r--r-- 1 root root 237 Май 28 01:28 255.in-addr.arpa -rw-r--r-- 1 root root 2994 Май 28 01:28 db.root -rw-r--r-- 1 root root 270 Май 28 01:28 localhost dns:~# cat /var/cache/bind/127.in-addr.arpa

BIND reverse data file for local loopback interface

$TTL 604800 @ IN SOA localhost. root.localhost. (

                             1         ; Serial
                             604800    ; Refresh
                             86400     ; Retry
                             2419200   ; Expire
                             604800 )  ; Negative Cache TTL

@ IN NS localhost. 1.0.0 IN PTR localhost. Рассматривать файлы "петлевых" и бродкастовых зон не вижу смысла, т.к. после установки пакета bind настройки заданные по умолчанию в данных файлах вполне приемлемы. Далее, при организации мастер сервера мы рассмотрим пример описания файла зоны. Хочу обратить внимание, что мы настраиваем кэширующий сервер, а определяем мы его и как master для некоторых из зон. В нашем случае "кэширующий" говорит о том, что наш сервер не поддерживает ни одну из реально существующих зон, т.е. ему не делегировано прав на такое обслуживание.

Да, чуть не забыл, демон named должен быть разрешен для запуска на необходимых уровнях выполнения ОС (команда в RedHat - /sbin/chkconfig bind9 on, в Debian - /usr/sbin/update-rc.d bind9 defaults). После изменения конфигурационных файлов можно добавить сервис в автозагрузку и запустить демон:

dns:~# update-rc.d bind9 defaults Adding system startup for /etc/init.d/bind9 ... /etc/rc0.d/K20bind9 -> ../init.d/bind9 /etc/rc1.d/K20bind9 -> ../init.d/bind9 /etc/rc6.d/K20bind9 -> ../init.d/bind9 /etc/rc2.d/S20bind9 -> ../init.d/bind9 /etc/rc3.d/S20bind9 -> ../init.d/bind9 /etc/rc4.d/S20bind9 -> ../init.d/bind9 /etc/rc5.d/S20bind9 -> ../init.d/bind9 dns:~# /etc/init.d/bind9 start Starting domain name service...: bind9. На этом настройка кэширующего DNS завершена. Все запросы, которые попадают в кэш DNS сервера он хранит в оперативной памяти компьютера и при перезапуске демона эти данные обнуляются. Для проверки работы кэша можно выполнить команду nslookup mail.ru example.com., если в ответе содержится строка Non-authoritative answer, то адрес пришел из кэша, а так же если выполнить dig www.ru. (или другой домен, которого еще нет в кэше) и через некоторое время повторить команду, то время ответа должно быть гораздо меньше.

Давайте рассмотрим другие варианты сервера.

Главный (master) сервер зоны

Основной конфиг содержит следующие настройки:

dns:~# cat /etc/bind/named.conf acl "lan" {

         192.168.1.1/24;
         127.0.0.1;

};

options {

         directory "/var/cache/bind";
         allow-query { any; };       // отвечать на зпросы со всех интерфейсов
         recursion no;               // запретить рекурсивные запросы
         auth-nxdomain no;           // для совместимости RFC1035
         listen-on-v6 { none; };     // IPv6 нам не нужен
         version "unknown";          // не отображать версию DNS сервера при ответах
         /*
         *  Раскомментируйте строки ниже, если
         *  хотите разрешить рекрусивные запросы
         *  из локальной сети.
         *  (так же, необходимо закомментировать
         *  recursion no; )
         */
         # forwarders {                 // указываем DNS сервера для пересылки
         #         83.239.0.202;        // предоставленные провайдером
         #         213.132.67.110;      // ибо до них ближе чем до корневых
         # };
         # allow-recursion { lan; };    // рекурсивные запросы тоже только из локальной

};

// описание настроек корневых серверов zone "." {

         type hint;
         file "db.root";

};

// нижеописанные зоны определяют сервер авторитетным для петлевых // интерфейсов, а так же для броадкаст-зон (согласно RFC 1912)

zone "localhost" {

         type master;
         file "localhost";

};

zone "127.in-addr.arpa" {

         type master;
         file "127.in-addr.arpa";

};

zone "0.in-addr.arpa" {

         type master;
         file "0.in-addr.arpa";

};

zone "255.in-addr.arpa" {

         type master;
         file "255.in-addr.arpa";

};

// описание основной зоны zone "example.com" {

         type master;
         file "example.com";
         allow-transfer { 10.0.0.191; };

};

//описание обратных зон zone "0.0.10.in-addr.arpa" {

         type master;
         file "0.0.10.in-addr.arpa";
         allow-transfer { 10.0.0.191; };

};

zone "1.168.192.in-addr.arpa" {

         type master;
         file "1.168.192.in-addr.arpa";
  1. allow-transfer { 10.0.0.191; }; // зона описывает локальную сеть поэтому ее не передаем

};

// настройки логирования logging {

         channel "misc" {
                   file "/var/log/bind/misc.log" versions 4 size 4m;
                   print-time yes;
                   print-severity yes;
                   print-category yes;
         };
         channel "query" {
                   file "/var/log/bind/query.log" versions 4 size 4m;
                   print-time yes;
                   print-severity no;
                   print-category no;
         };
         category default {
                   "misc";
         };
         category queries {
                   "query";
         };

}; Давайте кратко разберем конфигурационный файл и настройки master сервера: мы настраиваем мастер сервер для зоны example.com. . Согласно конфига, наш BIND имеет рабочий каталог /var/cache/bind, сервер отвечает на запросы со всех интерфейсов (allow-query {any ;};), рекурсивные запросы обрабатывает как итеративные (recursion no), является мастер-сервером для зоны example.com и локальных служебных зон (type master). При этом, если необходимо разрешить кэширование (то есть рекурсивные запросы) для локальной сети, то необходимо раскомментировать параметры forwarders и allow-recursion и закомментировать recursion no;.

Так же, для примера, я привел возможности BIND логировать все происходящее при работе сервера (можно для этой цели использовать syslog). В разделе logging задаются 2 параметра channel (можно и больше двух - на ваше усмотрение), эти параметры дословно можно назвать "канал" записи. Каждый канал определяет имя канала и настройки параметров записи (что записывать, а что - нет и куда писать). Директива category задает какую категорию сообщений в какой канал отправлять. Исходя из этого, мы имеем: запись стандартной информации в канал misc, а приходящие запросы посылаются в канал query. При этом, если файлы журнала достигают 4Мб (size 4m), он переименовывается добавлением к имени .1 и начинается запись в новый журнал, числа в конце других журналов увеличиваются. Журналы с номером, более указанного в version (в нашем случае 4) удаляются (Управлять ротацией логов можно так же с помощью logrotate). Параметры print* определяют заносить ли в журнал время появления, важность и категорию информации. Более подробно про настройки раздела logging можно почитать в man (5) named.conf.

Отдельно хочется описать параметр allow-transfer { 10.0.0.191; };. Данный параметр описывает серверы, которым разрешено скачивать копию зоны - т.н. slave серверА. В следующем примере мы разберем настройку slave DNS.

Для корректной работы логирования необходимо создать соответствующий каталог и присвоить необходимые права:

dns:~# mkdir /var/log/bind/ dns:~# chmod 744 /var/log/bind/ dns:~# ps aux | grep named bind 4298 0.0 3.4 46792 13272 ? Ssl Jul05 0:00 /usr/sbin/named -u bind root 4815 0.0 0.1 3304 772 pts/4 S+ 18:19 0:00 grep named dns:~# chown bind /var/log/bind/ dns:~# ls -ld /var/log/bind/ drwxr--r-- 2 bind root 4096 Июл 6 18:18 /var/log/bind/ Давайте далее рассмотрим наш файл описания зоны example.com.:

dns:~# cat /var/cache/bind/example.com $TTL 3D @ IN SOA ns.example.com. root.example.com. (

                                       2011070601      ; serial
                                       8H              ; refresh
                                       2H              ; retry
                                       2W              ; expire
                                       1D)             ; minimum

@ IN NS ns.example.com. @ IN NS ns2.example.com. @ IN A 10.0.0.152 @ IN MX 5 mx.example.com. ns IN A 10.0.0.152 ns2 IN A 10.0.0.191 mx IN A 10.0.0.152 www IN CNAME @ а так же в домене in-addr.arpa.

dns:~# cat /var/cache/bind/0.0.10.in-addr.arpa $TTL 3600 @ IN SOA ns.examle.com. root.example.com. (

            2007042001 ; Serial
            3600       ; Refresh
            900        ; Retry
            3600000    ; Expire
            3600 )     ; Minimum
       IN      NS      ns.examle.com.
       IN      NS      ns2.example.com.

152 IN PTR examle.com. 191 IN PTR ns.example.com.

  • IN PTR examle.com.

dns:~# cat /var/cache/bind/1.168.192.in-addr.arpa $TTL 3600 @ IN SOA ns.examle.com. root.example.com. (

            2007042001 ; Serial
            3600       ; Refresh
            900        ; Retry
            3600000    ; Expire
            3600 )     ; Minimum
       IN      NS      ns.examle.com.
       IN      NS      ns2.example.com.
  • IN PTR examle.com.

Наша сеть небольшая, предполагается, что в сети совсем мало машин. Все сервисы сети размещены на одном хосте example.com., поэтому и master DNS (ns.example.com.) и почтовый сервер (mx.example.com.) указывает на одну машину (10.0.0.152).

Вторичный (secondary, slave) авторитетный сервер зоны

Основная функция slave сервера - автоматическая синхронизация описания зоны с master сервером. Данная задача регламентируется документом RFC 1034 в разделе 4.3.5. Согласно данному документу обмен данными между серверами рекомендовано производить по протоколу TCP, посредством запроса AXFR. По этому запросу за одно TCP соединение должна передаваться вся зона целиком (RFC 1035).

Так же, slave DNS-сервер делит нагрузку с master сервером или принимает на себя всю нагрузку в случае аварии па первом сервере.

Прежде чем приступить к настройке slave DNS сервера, необходимо проверить возможность получения зоны вручную со вторичного сервера с помощью следующей команды:

root@debian:~# dig @10.0.0.152 example.com. axfr

<<>> DiG 9.7.3 <<>> @10.0.0.152 example.com. axfr
(1 server found)
global options
+cmd

example.com. 259200 IN SOA ns.example.com. root.example.com. 2011070801 28800 7200 1209600 86400 example.com. 259200 IN NS ns.example.com. example.com. 259200 IN NS ns2.example.com. example.com. 259200 IN A 10.0.0.152 example.com. 259200 IN MX 5 mx.example.com. mx.example.com. 259200 IN A 10.0.0.152 ns.example.com. 259200 IN A 10.0.0.152 ns2.example.com. 259200 IN A 10.0.0.191 www.example.com. 259200 IN CNAME example.com. example.com. 259200 IN SOA ns.example.com. root.example.com. 2011070801 28800 7200 1209600 86400

Query time
14 msec
SERVER
10.0.0.152#53(10.0.0.152)
WHEN
Fri Jul 8 15:33:54 2011
XFR size
11 records (messages 1, bytes 258)

Получение зоны прошло успешно. Далее, для настройки подчиненного сервера, алгоритм следующий:

Скопировать конфигурационный файл named.conf с master сервера; Заменить параметр type master на type slave в тех зонах, для которых он будет вторичным; Параметр allow-transfer { 10.0.0.191; }; заменить на masters { 10.0.0.152;}; в тех зонах, для которых он будет вторичным; Удалить зоны, которые не будет обслуживать текущий сервер, в том числе и корневую, если slave не будет отвечать на рекурсивные запросы; Создать каталоги для логов, как в предыдущем примере. Итого, мы получаем конфиг slave сервера:

root@debian:~# cat /etc/bind/named.conf options {

         directory "/var/cache/bind";
         allow-query { any; };      // отвечать на запросы со всех интерфейсов
         recursion no;              // запретить рекурсивные запросы
         auth-nxdomain no;          // для совместимости RFC1035
         listen-on-v6 { none; };    // IPv6 нам не нужен
         version "unknown";         // не отображать версию DNS сервера при ответах

};

// нижеописанные зоны определяют сервер авторитетным для петлевых // интерфейсов, а так же для броадкаст-зон (согласно RFC 1912)

zone "localhost" {

         type master;
         file "localhost";

};

zone "127.in-addr.arpa" {

         type master;
         file "127.in-addr.arpa";

};

zone "0.in-addr.arpa" {

         type master;
         file "0.in-addr.arpa";

};

zone "255.in-addr.arpa" {

         type master;
         file "255.in-addr.arpa";

};

// описание основной зоны zone "example.com" {

         type slave;
         file "example.com";
         masters { 10.0.0.152; };

};

//описание обратной зоны zone "0.0.10.in-addr.arpa" {

         type slave;
         file "0.0.10.in-addr.arpa";
         masters { 10.0.0.152; };

};

// настройки логирования logging {

         channel "misc" {
                   file "/var/log/bind/misc.log" versions 4 size 4m;
                   print-time YES;
                   print-severity YES;
                   print-category YES;
         };
         channel "query" {
                   file "/var/log/bind/query.log" versions 4 size 4m;
                   print-time YES;
                   print-severity NO;
                   print-category NO;
         };
         category default {
                   "misc";
         };
         category queries {
                   "query";
         };

}; после перезапуска наш slave сервер благополучно скопирует необходимую ему информацию с главного сервера, о чем будет говорить наличие файлов в каталоге:

root@debian:~# ls -la /var/cache/bind/ итого 28 drwxrwxr-x 2 root bind 4096 Июл 8 18:47 . drwxr-xr-x 10 root root 4096 Июл 8 15:17 .. -rw-r--r-- 1 bind bind 416 Июл 8 18:32 0.0.10.in-addr.arpa ...... -rw-r--r-- 1 bind bind 455 Июл 8 18:32 example.com ........ В принципе,/stroallow-transfer {pngp slave сервер может не хранить копию зоны у себя в файловой системе. Эта копия нужна только в момент старта DNS. Наличие копии зоны в файловой системе может избавить от сбоя при недоступности master сервера во время запуска slave DNS. Если не указать опцию file в разделе zone, то копия не создается.

Настройка netfilter (iptables) для DNS BIND

Собственно, настроив работу сервера, неплохо было бы его защитить. Мы знаем, что сервер работает на 53/udp порту. Почитав статью о том, что такое netfilter и правила iptables и ознакомившись с практическими примерами iptables, можно создать правила фильтрации сетевого трафика:

dns ~ # iptables-save

  1. типовые правила iptables для DNS
  • filter
INPUT DROP [7511:662704]
FORWARD DROP [0:0]
OUTPUT DROP [0:0]

-A INPUT -i lo -j ACCEPT -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A INPUT -m conntrack --ctstate INVALID -j DROP

  1. разрешить доступ локальной сети к DNS серверу:

-A INPUT -s 192.168.1.1/24 -d 192.168.1.1/32 -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT -A OUTPUT -o lo -j ACCEPT -A OUTPUT -p icmp -j ACCEPT -A OUTPUT -p udp -m udp --sport 32768:61000 -j ACCEPT -A OUTPUT -p tcp -m tcp --sport 32768:61000 -j ACCEPT -A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

  1. разрешить доступ DNS серверу совершать исходящие запросы

-A OUTPUT -p udp -m udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT COMMIT Это типовой пример! Для задания правил iptables под Ваши задачи и конфигурацию сети, необходимо понимать принцип работы netfilter в Linux, почитав вышеуказанные статьи.

Устранение неполадок

Основным источником для выявления проблем с DNS является системный лог. Вот пример ошибок при запуске, когда я ошибся с путем к файлу зоны коревых серверов:

Jul 5 18:12:43 dns-server named[4224]: starting BIND 9.7.3 -u bind Jul 5 18:12:43 dns-server named[4224]: built with '--prefix=/usr' '--mandir=/usr/share/man' '--infodir=/usr/share/info' '--sysconfdir=/etc/bind' '--localstatedir=/var' '--enable-threads' '--enable-largefile' '--with-libtool' '--enable-shared' '--enable-static' '--with-openssl=/usr' '--with-gssapi=/usr' '--with-gnu-ld' '--with-dlz-postgres=no' '--with-dlz-mysql=no' '--with-dlz-bdb=yes' '--with-dlz-filesystem=yes' '--with-dlz-ldap=yes' '--with-dlz-stub=yes' '--with-geoip=/usr' '--enable-ipv6' 'CFLAGS=-fno-strict-aliasing -DDIG_SIGCHASE -O2' 'LDFLAGS=' 'CPPFLAGS=' Jul 5 18:12:43 dns-server named[4224]: adjusted limit on open files from 1024 to 1048576 Jul 5 18:12:43 dns-server named[4224]: found 1 CPU, using 1 worker thread Jul 5 18:12:43 dns-server named[4224]: using up to 4096 sockets Jul 5 18:12:43 dns-server named[4224]: loading configuration from '/etc/bind/named.conf' Jul 5 18:12:43 dns-server named[4224]: reading built-in trusted keys from file '/etc/bind/bind.keys' Jul 5 18:12:43 dns-server named[4224]: using default UDP/IPv4 port range: [1024, 65535] Jul 5 18:12:43 dns-server named[4224]: using default UDP/IPv6 port range: [1024, 65535] Jul 5 18:12:43 dns-server named[4224]: listening on IPv4 interface lo, 127.0.0.1#53 Jul 5 18:12:43 dns-server named[4224]: listening on IPv4 interface eth1, 192.168.1.1#53 Jul 5 18:12:43 dns-server named[4224]: generating session key for dynamic DNS Jul 5 18:12:43 dns-server named[4224]: could not configure root hints from '/etc/bind/db.root': file not found Jul 5 18:12:43 dns-server named[4224]: loading configuration: file not found # файл не найден Jul 5 18:12:43 dns-server named[4224]: exiting (due to fatal error) Jul 5 18:15:05 dns-server named[4298]: starting BIND 9.7.3 -u bind Jul 5 18:15:05 dns-server named[4298]: built with '--prefix=/usr' '--mandir=/usr/share/man' '--infodir=/usr/share/info' '--sysconfdir=/etc/bind' '--localstatedir=/var' '--enable-threads' '--enable-largefile' '--with-libtool' '--enable-shared' '--enable-static' '--with-openssl=/usr' '--with-gssapi=/usr' '--with-gnu-ld' '--with-dlz-postgres=no' '--with-dlz-mysql=no' '--with-dlz-bdb=yes' '--with-dlz-filesystem=yes' '--with-dlz-ldap=yes' '--with-dlz-stub=yes' '--with-geoip=/usr' '--enable-ipv6' 'CFLAGS=-fno-strict-aliasing -DDIG_SIGCHASE -O2' 'LDFLAGS=' 'CPPFLAGS=' Jul 5 18:15:05 dns-server named[4298]: adjusted limit on open files from 1024 to 1048576 Jul 5 18:15:05 dns-server named[4298]: found 1 CPU, using 1 worker thread Jul 5 18:15:05 dns-server named[4298]: using up to 4096 sockets Jul 5 18:15:05 dns-server named[4298]: loading configuration from '/etc/bind/named.conf' Jul 5 18:15:05 dns-server named[4298]: using default UDP/IPv4 port range: [1024, 65535] Jul 5 18:15:05 dns-server named[4298]: using default UDP/IPv6 port range: [1024, 65535] Jul 5 18:15:05 dns-server named[4298]: listening on IPv4 interface lo, 127.0.0.1#53 Jul 5 18:15:05 dns-server named[4298]: listening on IPv4 interface eth1, 192.168.1.1#53 Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 254.169.IN-ADDR.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 2.0.192.IN-ADDR.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 100.51.198.IN-ADDR.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 113.0.203.IN-ADDR.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 255.255.255.255.IN-ADDR.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: D.F.IP6.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 8.E.F.IP6.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 9.E.F.IP6.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: A.E.F.IP6.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: B.E.F.IP6.ARPA Jul 5 18:15:05 dns-server named[4298]: automatic empty zone: 8.B.D.0.1.0.0.2.IP6.ARPA Jul 5 18:15:05 dns-server named[4298]: zone 0.in-addr.arpa/IN: loaded serial 1 Jul 5 18:15:05 dns-server named[4298]: zone 127.in-addr.arpa/IN: loaded serial 1 Jul 5 18:15:05 dns-server named[4298]: zone 255.in-addr.arpa/IN: loaded serial 1 Jul 5 18:15:05 dns-server named[4298]: zone localhost/IN: loaded serial 2 Jul 5 18:15:05 dns-server named[4298]: running # запуск прошел удачно Отличным инструментом для диагностики являются команды диагностики DNS.