Perl для системного администрирования

         

Файлы узлов Первый подход используемый


Теперь можно заняться более интересным делом - генерированием файлов узлов. Пусть у нас есть следующий файл базы данных для всех узлов в сети:

name: shimmer

address: 192.168.1.11

aliases: shim shimmy shimmydoodles

owner: David Davis

department: software

building: main

room: 909

manufacturer: Sun

model: Ultra60

name: bendir address: 192.168.1.3 aliases: ben bendoodles owner: Cindy Coltrane department: IT building: west room: 143

manufacturer: Apple model: 7500/100

name: Sulawesi address: 192.168.1.12 aliases: sula su-lee owner: Ellen Monk department: design building: main room: 1116 manufacturer: Apple model: 7500/100 name: sander address: 192.168.1.55 aliases: sandy micky mickydoo owner: Alex Rollins department: IT building: main room: 1101

manufacturer: Intergraph model: TD-325

Формат очень простой: имя_поля: значение, причем -=- используется в качестве разделителя между записями. Вероятно, вам потребуются иные поля или у вас будет слишком много записей, чтобы хранение их в одном «плоском» файле было оправдано. И хотя в этой главе применяется один плоский файл, принципы, приведенные здесь, не зависят от используемой базы данных.

Вот пример программы, которую можно применять для анализа подобного файла и генерирования файла узлов:

Sdatafile ="./database"; $recordsep = "-=-\n";

open(DATA,Sdatafile) or die "Невозможно открыть файл с данными:$!\п"; $/=$recordsep; и подготовка к чтению файла базы данных по одной записи

print "#\n# host file - GENERATED BY $0\n# DO NOT EDIT BY HAND!\n#\n"; while (<DATA>) {

chomp; и удалить разделитель записей



П разбить на key1,value1,...bingo, хэш записей

%record = split /:\s*|Wm;

print "$record{address}\t$record{name} $record{aliases}\n"; } close(DATA);

Вот что получается:

#

# host file - GENERATED BY createhosts

« DO NOT EDIT BY HAND!

192.168.1.11 shimmer shim shimmy shimmydoodles 192.168.1.3 bendir ben bendoodles

192.168.1.12 Sulawesi sula su-lee

1Q9 1RR 1 R^ sanrlpr чяпН\/ mir.k\/ rnirk\/r1nn

Теперь посмотрим на некоторые более интересные Perl-технологии ил этого небольшого отрывка программы. Первое необычное наше действие - установка переменной $,/. Начиная отсюда, Perl считает кусочки текста, заканчивающиеся символами - = -у\ одной записью. Это означает, что while за один раз прочитает всю запись и присвоит ее переменной $ .

Вторая интересная вещь - это технология присвоения значений средствами split. Наша цель состоит в получении хэша, ключами которого являются имена полей, а значениями - значения полей. Зачем нам это надо, станет понятно позже, когда мы будем расширять пример. Первый шаг заключается в разбиении $_ на части при помощи spiuO. Массив, который получается в результате работы split(), приведен в табл. 5.1.



Информация о модулях из этой главы



Информация о модулях из этой главы

Модуль Идентификатор на CPAN Версия
Res CFRETER 0.09
Net: :NIS RIK а2
Data : : Dumper (входит в состав Perl) GSAR 2.101
10 : : Socket (входит в состав Perl) GBARR 1.20
Net:: DNS MFUHR 0.12


Использование Net DNS



Использование Net::DNS

Как уже говорилось в главе 1, одна из сильных сторон Perl заключается в поддержке обширным сообществом разработчиков, создающих программы, которые могут применяться другими людьми. Если необходимо сделать на Perl нечто, на ваш взгляд, универсальное, то высока вероятность того, что кто-то уже написал модуль для работы с подобной проблемой. В данном случае можно воспользоваться отличным модулем Net: :DNS Майкла Фура (Michael Fuhr), который упростит работу. Чтобы справиться с нашей задачей, необходимо создать новый объект, передать ему имя DNS-сервера, к которому следует обратиться, указать, что нужно послать запрос, и затем применить имеющиеся методы для анализа ответов:

use Net::DNS;

&lookupaddress($hostname,$server); # заполняем значениями %results }

%inv = reverse %results; » инвертируем полученный хэш if (scalar(keys %inv) > 1) { tt проверяем, сколько в нем элементов

print "Между DNS-серверами есть разногласия:\п";

use Data:: Dumper;

print Data::Dumper->Dump([\%results],["results"]),"\n"; }

tt всего лишь несколько измененный пример из страниц руководства по Net::DNS sub lookupaddress{

my($hostname,$server) = @_;

$res = new Net::DNS::Resolver; $res->nameservers($server); Spacket = $res->query($hostname);

if (!$packet) {

warn "Невозможно получить данные о Shostname с $server!\n";

return; }

# сохраняем последний полученный ответ RR foreach $rr ($packet->answer) {

$results{$server}=$rr->address;

}

}

Преимущества такого подхода:

Помимо прочего, получаемый код легко читать. Написать его можно быстрее. В зависимости от того, как реализован применяемый модуль (только на Perl или с использованием библиотечных вызовов из С или C++), написанная программа может выполняться так же быстро, как и вызов внешней программы. Потенциально, это переносимая программа - все зависит только от того, что именно сделал автор модуля. Везде, где можно установить модуль, программа будет работать. Как и в первом рассмотренном случае, написать программу можно быстро и просто, если кто-то другой сделает за вас всю работу, происходящую «за сценой». Вам не нужно знать, как работает модуль; вы только должны знать, как его применять. Код используется повторно. Нет необходимости каждый раз изобретать велосипед.

Недостатки данного подхода:

Снова появилась зависимость. На этот раз необходимо убедиться, что модуль доступен вашей программе. Приходится поверить, что автор модуля проделал хорошую работу. Может не существовать подходящего вам модуля или он может не запуститься на выбранной вами операционной системе.

В большинстве случаев я предпочитаю использовать уже существующие модули. Тем не менее, для выполнения поставленной задачи подойдет любой подход. Существует несколько способов сделать одно и то же - значит, вперед, действуйте!



Использование nslookup



Использование nslookup

Если у вас есть опыт работы в Unix или вы уже программировали на других языках сценариев помимо Perl, то первая попытка может сильно походить на сценарий командного интерпретатора. Внешняя программа, вызываемая из Perl сценария, выполняет всю сложную работу:

use Data::Dumper;

Shostname = $ARGV[0];

Snslookup = "/usr/local/bin/nslookup";

# путь к nslookup ©servers = qw(nameserver1 nameserver2 nameserverS);

имена серверов имен foreach Sserver (©servers) {

&lookupaddress($hostname,Sserver); в заполняем %results }

%inv = reverse %results;

# инвертируем полученный хэш

if (scalar(keys %inv) > 1) {

print "Между DNS-серверами есть разногласияДп";

print Data::Dumper->Dump([\%results],["results"]),"\n"; }

» обращаемся к серверу, чтобы получить IP-адрес и прочую

# информацию для имени узла, переданного в программу в

командной строке. Результаты записываем в хэш %results sub lookupaddress

my($hostname,Sserver) = @_;

open(NSLOOK,"$nslookup Shostname Sserver|") or

die "Невозможно запустить nslookup:$!\n";

while (<NSLOOK>) {

« игнорировать, пока не дойдем до "Name: next until (/"Name:/);

следующая строка - это ответ Address: chomp($results{$server} = <NSLOOK>);

# удаляем имя поля

die "Ошибка, вывода nslookup \n" unless /Address/;

$results{$server} =" s/Addfess(es)?:\s+//;

все, с nslookup мы закончили last;

}

close(NSLOOK);

}

Преимущества такого подхода:

Это короткая программа, которую можно быстро написать (вероятно, ее даже можно построчно перевести из настоящего сценария командного интерпретатора). Нет необходимости писать запутанный код для работы с сетью. Применяется подход в стиле Unix, когда язык общего назначения используется для соединения нескольких маленьких специальных программ, выполняющих требуемые задачи в связке, вместо того чтобы писать большую монолитную программу. Это может оказаться единственным выходом, если нельзя написать программу для взаимодействия клиента и сервера на Perl; в частности, если вам нужно обратиться к серверу, который требует использования особого клиента без каких-либо альтернатив.

Недостатки такого подхода:

Появляется зависимость от другой программы за пределами сценария. А если эта программа недоступна? Что делать, если изменится формат вывода данной программы? Это работает медленнее. Каждый раз, для того чтобы выполнить запрос, необходимо запустить новый процесс. От подобной нагрузки можно избавиться, если установить двунаправленный канал с процессом nslookup, который открыт все то время, когда он нужен. Правда, это потребует несколько большего опыта программирования, но это стоит сделать, если вы решите использовать и улучшать подобный подход. У вас меньше контроля. Во всех деталях реализации приходится полагаться на милость внешней программы. Например, в данном случае nslookup (если быть более точным, то библиотека разыменования, которую использует nslookup) обрабатывает тайм-ауты сервера, повторные попытки запросов и дописывает списки поисков доменов.

MIS NIS+ и WINS Разработчики из


В состав операционной системы Solaris входит NISH— следующая версия NIS. В NIS+ решены многие из наиболее серьезных проблем, которые были в NIS, в частности, проблема безопасности. К сожалению (а может быть и к счастью, т. к. NIS+ администрировать несколько NIS, NIS+nWINS 181

сложнее), NIS+ не стала столь популярной в мире Unix, как NIS. До недавнего времени она практически не поддерживалась на машинах, созданных не в Sun. NIS+ постепенно «приживается» в стандартных дистрибутивах Linux, благодаря работе Торстена Кукука (Thorsten Kukuk) (http://www.suse.de/~kukuk/nisplus/index.html), но она отнюдь не преобладает в мире Unix и ее просто не существует в NT и MacOS.

Принимая во внимание то, что NIS+ используется незначительно, говорить о ней в книге мы больше не будем. Если вам необходимо работать с NIS+ из Perl, можете применять еще один модуль Хариса - Net::NISPlus.



Проверка ошибок в процессе генерирования файла узлов



Проверка ошибок в процессе генерирования файла узлов

Напечатать нужные части - это только начало. Значительное преимущество употребления отдельной базы данных, которая преобразовывается в другую форму, состоит в возможности выполнения проверки ошибок во время преобразования. Раньше уже отмечалось, что подобный контроль позволяет избавиться от проблем, вызванных такими мелкими неприятностями, как опечатки, еще до того, как данные будут распространены. Вот как будет выглядеть предыдущий пример, если в него добавить проверку опечаток:

Sdatafile ="./dataoase":

Srecorcsep = "- = -\;n:

# по одной записи за один раз

print "#\n\# host file - GENERATED BY $0\n# DO NOT EDIT BY HAND!\n#\n";
while (<DATA>) {

chomp; n удаляем разделитель записей

# разбиваем на key1,value1,... bingo, хэш записей
%record = split /:\s*|\n/m;

# проверка на неверные имена узлов

if ($record{name} =" /["-.a-zA-ZO-9]/) {
warn "!!!! $record{name} содержит недопустимые для имени узла символы

пропускаем...\п";
next;
}

# проверка на неверные псевдонимы

if ($record{aliases} =" /[~-.a-zA-ZO-9\s]/) {
warn "!!!! $record{name} содержит недопустимые для псевдонима символы,

пропускаем...\п";
next;
}

# проверка на пропущенные адреса
if (! $record{address» {

warn "!!!! $record{name} не имеет IP-адреса, пропускаем...\n";
next;
}

tt проверка на одинаковые адреса

if (defined $addrs{$record{address}>) {

warn "!!!! Дублируется IP-адрес: $record{name} &
$addrs{$record{address}}, пропускаем...\n";

next:
}
else {

$addrs{$record{address}} = $record{name};
}

print "$record{address}\t$record{name} $record{aliases}\n";
}
ClOse(DATA);

Улучшение полученного файла узлов

Позаимствуем из главы 9 «Журналы» процесс анализа выполняемого преобразования. Мы можем автоматически добавить полезные заголовки, комментарии и разделители к получаемым данным. Вот как выглядит результат преобразования той же самой базы данных:

#

К host file - GENERATED BY createhosts3 » DO NOT EDIT BY HAND!

Converted by David N. Blank-Edelman (dnb) on Sun Jun 7 00:43:24 1998

# number of hosts in the design department: 1.

ft number of hosts in the software department: 1.

# number of hosts in the IT department: 2.

# total number of hosts: 4
#

tt Owned by Cindy Coltrane (IT): west/143

if ($record->{aliases) =" /[~-.a-zA-ZO-9\s]/) {

warn "MM ". $record->{name} '.

содержит недопустимые для псевдонима символы, пропускаем,..\n";

next; }

tt проверка на пропущенные адреса if (!$record->{address}) {

warn "!!! ! ".$record->{name} .

не имеет IP-адреса, пропускаем,.Дл";

next; >

# проверка на совпадающие адреса

if (defined $addrs{$record->{address}}) {

warn "Ml! Дублируется IP-адрес:". $record->{name}.

" & ".$addrs{$record->{address}}.", пропускаем.. ";

next; 1 else {

$addrs{$record->{address}} = $record-><name}; }

$entries{$record->{name}} = $record; n добавляем это в хэш

tt хэшей } close(DATA);

It печатаем симпатичный заголовок

print "#\n\« host file - GENERATED BY $0\n# DO NOT EDIT BY HAND!\nff\n";

print "« Converted by $user on ".scalar(localtime). "\ntt\n";

# подсчитываем число записей для каждого отдела

Я и сообщаем об этом

foreach my Sentry (keys %entries){

Sdepts{Sentries{Sentry}->{department}}++; }

foreach my Sdept (keys %depts) {

print "n number of hosts in the

Sdept department: $depts{$dept},\n"; )

print "tt total number of hosts: ".

scalar(keys %entries). "\n#\n\n";

tt обходим в цикле все узлы, выводя комментарий и саму запись f

oreach my Sentry (keys %entries) {

print "tt Owned by ", $entries{$entry}->{owner}," (",

$entries{$entry>->{department},"): ",

Sentries{Sentry}->{building}."/",

Sentries{Sentry}->{room},"\n";

print $entries{$entry}->{address},"\t",

$entries{$entry}->{name}." ",

Sentries{Sentry}->{aliases},"\n\n"; }

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

Можно было сохранять отдельную хэш-таблицу для каждого поля (подобно тому, как это было сделано в примере needspace из главы 2 «Файловые системы»), но красота приведенного метода состоит в его поддерживаемости. Если затем понадобится добавить в базу данных поле serial_number, нам не придется менять используемый для анализа файла код, это поле само по себе появится. Недостаток же в том, что синтаксис Perl таков, что наш код выглядит более сложным, чем он есть на самом деле.

Посмотреть на все это можно еще проще: мы будем анализировать файл точно так же, как и в предыдущем примере. Разница лишь в том, что каждую запись мы будем сохранять в новом анонимном хэше. Анонимные хэши ничем не отличаются от обычных, только обращаться к ним приходится не по имени, а по ссылке.

Чтобы построить большую структуру данных (хэш хэшей), достаточно связать каждый новый анонимный хэш с основной хэш-таблицей %entries и создать ключ со связанным с ним значением, являющимся ссылкой на этот только что заполненный анонимный хэш. Когда каждый хэш пройдет обработку, ключами %entries будут имена всех машин, а значениями - ссылки на хэш-таблицы, содержащие значения всех полей, связанных с этим именем (IP-адрес, номер кабинета и т. д.).

Вероятно, вам бы хотелось, чтобы вывод был отсортирован по IP-адресам? Никаких вопросов, просто добавьте процедуру сортировки, изменив:

foreach my Sentry (keys %entries) { на:

foreach my Sentry (sort byaddress keys %entries) { и добавьте:

sub byaddress {

@a = split(/\./,$entries{$a}->{address}):

@b = split(/\./.$e"tries{$b}-''{address)):

($a[0]<=>$b[OJ) ,!

($а[1]<=>$Ь[1!) ! i

($a[2]<=>$b[21) !|

($a[3]<=>$D[3]l;

Вот как будут выглядеть отсортированные данные:

И Owned by Cindy Coltranc (IT): west/143 192.168.1,3

tjendir ben bei.doooles

П Owned by David Davis (software): inai'i/909

192.168.1.11 shimmer snm siimr, sniiMiydoodies

n Owned by Ellen Monk (design): rain/1116

192.168.1.12 Sulawesi sula su-lee

# Owned by Alex Rollins (IT): rnain/1101 192.168.1.55

sander sandy micky mickydoo

Сделайте так, чтобы полученные данные вам нравились. Пусть Perl поддержит ваши профессиональные и эстетические стремления.



Проверка работы DNS итеративный подход



Проверка работы DNS: итеративный подход

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

Например, для системного/сетевого администратора очень многое зависит от ответа на вопрос «Все ли DNS-серверы работают?». В ситуации, когда необходимо найти неисправности, практически настолько же важно знать, «Все ли серверы работают с одной и той же информацией?», или, более точно, «Отвечают ли они одинаково на одинаковые запросы? Синхронизированы ли они?». Данный раздел посвящен подобным вопросам.

По главе 2 можно судить, как действует основной принцип Perl «Всегда существует несколько способов сделать это». Именно такое свойство делает Perl отличным языком для «итеративной разработки». Итеративная разработка - это один из способов описания эволюционного процесса, имеющего место при создании программ системного администрирования (и не только), выполняющих определенную задачу. В случае с Perl можно быстро написать рабочую программу на скорую руку, а позднее вернуться к сценарию и переписать его более элегантным образом. Возможно, будет еще и третья итерация, на этот раз уже с использованием другого подхода к решению задачи.

Существует три различных подхода к одной и той же проблеме проверки согласованности DNS. Они представлены в том порядке, которому, действительно, мог бы последовать человек, пытаясь найти решение, а затем его совершенствуя. Этот порядок отражает взгляд на то, как решение проблемы может развиваться в Perl; ибо ваше отношение к подходу может меняться. Третий способ, использующий модуль Net: : DNS, вероятно, самый простой и наиболее защищенный от ошибок. Но существуют ситуации, когда Net: : DNS применять нельзя, поэтому сначала приведем несколько собственных решений. Обязательно обратите внимание на все за и против, перечисленные после каждого рассмотренного подхода.

Вот наша задача: написать сценарий на Perl, принимающий имя узла и проверяющий список DNS-серверов, чтобы убедиться, что все они возвращают одну и ту же информацию об узле. Чтобы упростить задачу, будем считать, что узел имеет единственный статический IP-адрес (т. е. у него один сетевой адаптер и один IP-адрес).

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

$hostname = $ARGV[0];

©servers = qw(nameserver1 nameserver2 nameserverS);

# серверы имен

foreach $server (servers) {

&lookupadrjress($hostname, $server);

заполняем %results

}

%inv = reverse %results;

# инвертируем полученный хэш

if (keys %inv > 1) {

print "Между DNS-серверами есть разногласия";

use Data::Dumper;

print Data::Dumper->Dump([\%results],["results"]), "\n"; }

Для каждого из DNS-серверов, перечисленных в списке @servers, вызывается подпрограмма &lookupaddress(), которая обращается к DNS-серверу, чтобы получить IP-адрес заданного имени узла, и помещает результаты в хэш %results. Для каждого DNS-сервера в хэше %results есть запись, значением которой является IP-адрес, возвращаемый этим сервером (ключом является имя сервера).

Существует много способов определить, равны ли друг другу значения из хэша %results (т. е. убедиться, что все DNS-серверы возвращают одну и ту же информацию в ответ на запрос). Мы инвертируем хэш %results в другую хэш-таблицу, преобразовывая все ключи в значения и наоборот. Если все значения из %results одинаковы, то в инвертированном хэше должен быть только один ключ. Если ключей несколько, значит, мы выловили прокол, и поэтому вызываем Data: :Duniper->Durrip() для СлужОа доменных имен вывода содержимого %results, над которым будет ломать голову системный администратор.

Вот как может выглядеть примерный результат, если что-то идет не так:

Между DNS-серверами есть разногласия: $results = {

nameserverl => '192.168.1.2',

nameserver2 => '192. 168. 1.5' ,

nameserverS => ' 192. 168. 1.2' ,

Теперь посмотрим на альтернативы подпрограмме &lookupaddress( ).



Работа напрямую с сетевыми сокетами



Работа напрямую с сетевыми сокетами

Если вы «продвинутый системный администратор», вы можете решить, что вызывать внешнюю программу не следует. Вы можете захотеть реализовать запросы к DNS, не используя ничего, кроме Perl. Это означает, что нужно будет создавать вручную сетевые пакеты, передавать их по сети и затем анализировать результаты, получаемые от сервера.

Вероятно, это самый сложный пример из всех, приведенных в книге. Написан он после обращения к дополнительным источникам информации, в которых можно найти несколько примеров существующего кода (включая модуль Майкла Фура (Michael Fuhr), показанный в следующем разделе). Вот что происходит на самом деле. Запрос к DNS-серверу состоит из создания специального сетевого пакета с определенным заголовком и содержимым, отправки его на DNS-сервер, получения ответа от сервера и его анализа.

Каждый DNS-пакет (из тех, которые нас интересуют) может иметь до пяти различных разделов:

Header(Заголовок)

Содержит флаги и счетчики, относящиеся к запросу или ответу (присутствует всегда).

Question (Запрос)

Содержит вопрос к серверу (присутствует в запросе и повторяется при ответе).

Answer (Ответ)

Содержит все данные для ответа на DNS-запрос (присутствует в пакете DNS-ответа).

Authority (Полномочия)

Содержит информацию о том, можно ли получать авторитетные ответы.

Additional (Дополнительно)

Содержит любую информацию, которую вернет сервер помимо прямого ответа на вопрос.

Наша программа имеет дело только с первыми тремя из этих разделов. Для создания необходимой структуры данных для заголовка DNS-na-кета и его содержимого используется набор команд oack(). Эти структуры данных передаются модулю 10: :Socket, который посылает их в виде пакета. Этот же модуль получает ответ и возвращает его для обработки (при помощи unpackO). Умозрительно такой процесс не очень сложен.

Но перед тем как посмотреть на саму программу, нужно сказать об одной особенности в этом процессе. В RFC1035 (Раздел 4.1.4) определяются два способа представления доменных имен в DNS-пакетах: несжатые и сжатые. Под несжатым доменным именем подразумевается полное имя домена (например host.oog.org) в пакете. Этот способ ничем не примечателен. Но если это же доменное имя встретится в пакете еще несколько раз, то, скорее всего, оно будет представлено в сжатом виде во всех вхождениях, кроме первого. В сжатом представлении информация (или ее часть) о домене заменяется двубайтовым указателем на несжатое представление этого же доменного имени. Это позволяет использовать в пакете hostl, host2 и hostS в longsubdomain.longsubdomain.oog.org, вместо того чтобы каждый раз включать лишние байты для longsubdo-main.longsubdomain.oog.org. Нам необходимо обработать оба представления, поэтому и существует подпрограмма &decompress. Дальше обойдемся без фанфар и взглянем на код:

use 10: '.Socket;

Shostname = $ARGV[0];

$defdomain = ".oog.org"; # домен по умолчанию

^servers = qw(nameserver1 nameserver2 nameserverS);

имена серверов имен
foreach Iserver (©servers) {

<Slookupaddress($nostname,$server);

# записываем значения в
%results
}

%inv = reverse ^results; # инвертируем полученный хэш
if (scalar(keys %inv) > 1) { # проверяем, сколько в нем элементов

print "Между DNS-серверами есть разногласия:\п";

use Data::Dumper;

print Data::Dumper->Dump([\%results],["results"]),"\n";
}

sub lookupaddress{

my($hostname,$server) = @_;

my($qname,$rna(tre,$header,$question,$lformat,@>labels,$count);
local($position,$buf);

Конструируем заголовок пакета

$header = pack("n C2 n4",

++$id, # идентификатор запроса

1, # поля qr, opcode, aa, tc, rd (установлено только rd)

0, # rd, ra

1, один вопрос (qdcount)
0, нет ответов (ancount)

О, п нет записей ns в разделе authority (nscount)
0); tf нет rr addtl (arcount)

если в имени узла нет разделителей,
дописываем домен по умолчанию
(index($hostname,'.') == -1) {

Shostname .= Sdefdomain;
} # конструируем раздел qname пакета (требуемое доменное имя)
for (split(/\./,$riostname)) {

$lformat .= "С а* ";

$labels[$count++]=length;

$labels[$count++]=$_;
}

да конструируем вопрос
да

Squestion = pack($lformat."С п2",
©labels,

0, # конец меток

1, # qtype A
1); # qclass IN

да

да посылаем пакет серверу и читаем ответ

$sock = new 10::Socket::INET(PeerAddr => Sserver,

PeerPort => "domain",

Proto => "udp");

$sock->send($header.$question);

используется UDP, так что максимальный размер пакета известен

$sock->recv($buf,512);

close($sock);

узнаем размер ответа, так как мы собираемся отслеживать

позицию в пакете при его анализе (через Sposition)
Srespsize = length($buf);

распаковываем раздел заголовка

да

($id,

$qr_opcode_aa_tc_rd,

$rd_ra,

Sqdcount,

$ancount,

Snscount,

Sarcount) = unpack("n C2 n4",$buf);

if (!$ancount) <

warn "Невозможно получить информацию для $hostname с Sserver!\n";

return;
}

распаковываем раздел вопроса

tt раздел вопроса начинается после 12 байтов

($position,$qname) = <Sdecompress(12);

($qtype,$qclass)=unpack('§'.Sposition.'n2',Sbuf);

tt переходим к концу вопроса

Sposition += 4;

nntt

tttttt распаковываем все записи о ресурсах

ttntt

for ( ;$ancount;$ancount--){

(Sposition,$rname) = &decompress($position);

(Srtype,Srclass,$rttl,$rdlength)=

unpack('@'.Sposition.'n2 N n',$buf);

Sposition +=10;

tt следующую строку можно изменить и использовать более

# сложную структуру данных; сейчас мы подбираем

последнюю возвращенную запись

$results{$server}=

join('.',unpack('@'.Sposition.'C'.$rdlength,$buf));

Sposition +=$rdlength; } >

О обрабатываем информацию, "сжатую" в соответствии с RFC1035

# мы переходим в первую позицию в пакете и возвращаем

# найденное там имя (после того как разберемся с указателем

# сжатого формата) и место, которое мы оставили в конце

# найденного имени sub decompress {

my($start) = $_[0]; my($domain,$i,Slenoct);

for ($i=$start;$i<=$respsize;) {

$lenoct=unpack('@'.$i.'C', $buf); n длина метки

if (! Slenoct){ tt 0 означает, что этот раздел обработан

$i++;

last; }

if (Slenoct == 192) { tt встретили указатель,

tt следовательно, выполняем рекурсию

Sdomain.=(&decompress((unpack('@'.$i.'n',$buf) & 1023)))[1];

$i+=2;

last } else { tt в противном случае это простая метка

$domain.=unpack('@г.++$i.'a'.Slenoct,$buf).'. ';

$i += Slenoct; }

return($i,Sdomain);

}

Надо заметить, что эта программа не является точным эквивалентом предыдущего примера, потому что мы не пытаемся эмулировать все нюансы поведения nslookup (тайм-ауты, повторные попытки и списки поиска). Рассматривая все три подхода, представленные здесь, обязательно обратите внимание на такие различия.

Преимущества этого подхода заключаются в следующем:

Он не зависит от каких-либо других программ. Нет необходимости разбираться в работе других людей. Это настолько же быстро, а может быть, и еще быстрее, чем вызов внешней программы. Проще обработать параметры ситуации (тайм-ауты и прочее). Недостатки же такого подхода в том, что: Для написания подобной программы понадобится больше времени и, кроме того, она сложнее предыдущей. Этот подход требует дополнительных знаний, не имеющих прямого отношения к вашей задаче (т. е. вам, возможно, потребуется узнать, как вручную собирать DNS-пакеты, чего при использовании nslookup знать было не нужно). Вам, вероятно, придется самостоятельно справиться с различиями между операционными системами (в предыдущем подходе они были скрыты благодаря тому, что эту работу выполнил автор внешней программы).

Рекомендуемая дополнительная литература



Рекомендуемая дополнительная литература

«DNS and BIND»,

3rd Edition, Paul Albitz, Cricket Liu (O'Reilly, 1998).

«RFC849: Suggestions For Improved Host Table Distribution»,

Mark Crispin, 1983.

«RFC881: The Domain Names Plan and Schedule»,

J. Postel, 1983.

«RFC882: Domain Names: Concepts And Facilities»,

P. Mockapetris, 1983.

«RFC1035: Domain Names: Implementation And Specification»,

P. Mockapetris, 1987.

Служба доменных имен (DNS) Несмотря


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

Данные хранятся в отдельной базе данных (одна и та же база может и, вероятно, должна быть источником для всех файлов, о которых идет речь). Данные преобразуются в формат вывода по нашему выбору, при этом проверяются ошибки. Используется RCS (или эквивалентная система контроля версий) для хранения предыдущих версий файлов.

В случае с DNS второй шаг необходимо расширить, поскольку здесь процесс преобразования оказывается более сложным. Сложности нужно преодолевать, поэтому было бы неплохо иметь под рукой книгу Пола Альбица (Paul Albitz) и Крикета Лью (Cricket Liu) DNS and BIND («DNS и BIND», O'Reilly), содержащую, в том числе, сведения о конфигурационных файлах, создание которых рассматривается ниже.



Службы имен TCP/IP



Службы имен TCP/IP

Файлы, узлов NIS, NIS+ u WINS Служба доменных имен (DNS) Информация о модулях из этой главы Рекомендуемая дополнительная литература

В настоящее время большая часть «разговоров» между компьютерами происходит по протоколу управления передачей (Transmission Control Protocol), который, в свою очередь, выполняется на более низком уровне, называемом межсетевым протоколом (Internet Protocol). Эти два протокола обычно объединяются в один акроним TCP/IP. Каждой машине в TCP/IP-сети должен быть присвоен хотя бы один уникальный численный идентификатор, называемый IP-адресом. IP-адреса обычно записываются в формате NNN.NNN.N.N, например, 192.168.1.9.

В то время как машины без затруднений обращаются друг к другу при помощи строк чисел, разделенных точками, большинство людей от этой идеи не в восторге. TCP/IP потерпел бы полное фиаско как протокол, если бы пользователям пришлось запоминать уникальные последовательности из 12 цифр для каждой машины, к которой они обращаются. Необходимо было придумать механизмы для преобразования IP-адресов в имена, понятные людям.

В этой главе рассказано об эволюции сетевых служб имен, позволяющих обращаться к данным на сайте www.oog.org, а не 192.168.1.9, а также о том, что происходит за сценой. По ходу дела мы будем сопровождать примеры из истории изрядным количеством полезных советов о том, как Perl помогает работать с этой важной частью любой сетевой инфраструктуры.



Создание административного заголовка



Создание административного заголовка

Конфигурационные файлы DNS начинаются с административного заголовка, в нем представлена информация о сервере и данных, которые он обслуживает. Самая важная часть этого заголовка - запись о ресурсах SOA (Start of Authority). Запись SOA содержит:

Имя административного домена, обслуживаемого данным DNS-cep-вером. Имя первичного DNS-сервера этого домена. Контактную информацию об администраторе (администраторах) DNS-сервера. Порядковый номер конфигурационного файла (подробнее об этом рассказывается чуть ниже). Значения тайм-аутов регенерации (refresh) и повторного обращения (retry) для вспомогательных серверов (т. е. информация о том, когда необходимо синхронизировать данные с первичным сервером). Время жизни (TTL) для данных (т. е. в течение какого времени можно безопасно кэшировать информацию).

Вот как может выглядеть этот заголовок:

@ IN SOA dns.oog.org. hostmaster.oog.org. (

1998052900 ; serial

10800 ; refresh 3

600 ; retry

604800 ; expire

43200) ; TTL

@ IN NS dns.oog.org.

Булыпая часть информации добавляется в начало конфигурационного файла каждый раз при его создании. Единственное, о чем нужно побеспокоиться, - это о порядковом номере. Один раз в X секунд (X определяется из значения регенерации) вторичные серверы имен сверяются с первичными серверами, чтобы узнать, нужно ли обновить данные. Современные вторичные DNS-серверы (подобные BIND v8+ или Microsoft DNS) могут быть сконфигурированы так, что будут сверяться с основным сервером в то время, когда на последнем меняются данные. В обоих случаях вторичный сервер запрашивает на первичном запись SOA. Если порядковый номер записи SOA первичного сервера больше порядкового номера, хранимого на вторичном сервере, то произойдет перенос информации о зоне (вторичный сервер загрузит новые данные), В итоге, важно увеличивать порядковый номер каждый раз при создании нового конфигурационного файла. Многие из проблем с DNS вызваны неполадками при обновлении порядкового номера. Существует по крайней мере два способа сделать так, чтобы порядковый номер всегда увеличивался:

Считывать предыдущий конфигурационный файл и увеличивать найденное там значение. Вычислять новое значение, основываясь на внешних данных, которые «гарантированно» увеличиваются (это могут быть, например, системные часы или номера версий файла в RCS).

Ниже приведен пример программы, где применяется комбинация этих двух методов для создания допустимого заголовка файла зоны DNS. Порядковый номер будет представлен в виде, который рекомендуют использовать Альбиц и Лью в своей книге (YYYYMMDDXX, где Y=rofl, М=месяц, В=день и ХХ=двузначный счетчик, позволяющий вносить более одного изменения за день):

ft получаем текущую дату в формате YYYYMMDD

@localtime = localtime;

Stoday = sprintf("%04d%02d%02d",$localtime[5]+1900,

$localtime[4]+1,

$localtime[3]);

# имя пользователя как в NT/2000, так и в Unix

$user = ($"0 eq "MSWin32")? $ENV{USERNAME} :

(getpwuid($<))[6]." (".(getpwuid($<))[0].")";

sub GenerateHeader{ my($header);

открываем старый файл, если это возможно, и считываем

# порядковый номер, принимая во внимание формат старого файла

if (open (OLDZONE,$target)){ while (<OLDZONE>) {

next unless (/(\d{8}).«serial/); Soldserial = $1; last; }

close (OLDZONE); } else {

Soldserial = "00000000";

иначе начинаем с О >

К если предыдущий порядковый номер соответствует

# сегодняшнему дню, то увеличиваем последние 2 цифры, в

# противном случае используем новый номер для сегодняшнего дня

Solddate = substr($oldserial,0,8);

Scount = ((Solddate == $today) ? substr($oldserial,8,2)+1 : 0);

Sserial = sprintf("%8d%02d",$today,Scount);

П начало заголовка

$header .= "; Файл зоны dns - СОЗДАН

$0\л": Sheader .= ";

НЕ РЕДАКТИРУЙТЕ ВРУЧНУЮ1\п:\п"; Sheader .= ";

преобразован пользователем $user в ".scalar((localtime)). "\n;\n";

# пересчитать число записей для каждого отдела и сообщить

foreach my Sentry (keys %entries){

$depts{$entries{$entry}->{department}}++;

}

foreach my $dept (keys %depts) {

Sheader .= "; число узлов в отделе Sdept:

$depts{$dept}.\n"; } Sheader .= ";

всего узлов: ",scalar(keys %entries)."\n;\n\n";

Sheader .= «"EOH";

@ IN SOA dns.oog.org. hostmaster.oog.org. (

Sserial ; serial 10800 ; refresh 3600 ; retry 604800 ; expire 43200) ; TTL

@ IN NS dns.oog.org.

EOH

return Sheader;

}

В примере осуществляется попытка прочитать предыдущий конфигурационный файл для определения последнего порядкового номера. Затем это значение разбивается на поля даты и счетчика. Если прочитанная дата совпадает с текущей, необходимо увеличить значение счетчика. Если нет, то в новом порядковом номере поле даты совпадает с текущей датой, а значение счетчика равно 00. Теперь, когда порядковый номер проверен, можно вывести заголовок в правильном виде.



Создание нескольких конфигурационных файлов



Создание нескольких конфигурационных файлов

После того как написан верный заголовок для конфигурационных файлов, осталось решить еще одну проблему. Правильно настроенный DNS-c-зрвер поддерживает как прямое преобразование (имен в IP-адреса), так и обратное преобразование (IP-адресов в имена) для каждого домена (или зоны), который он обслуживает. Для этого надо иметь два конфигурационных файла на каждую зону. Самый лучший способ их синхронизировать - создавать файлы в одно и то же время.

Рассмотрим в данной главе последний пример генерирования файлов, поэтому соберем воедино все, что обсуждали раньше. Приведенный сценарий использует для создания конфигурационных файлов зоны DNS простой файл базы данных.

Чтобы не усложнять сценарий, я сделал ряд предположений относительно данных, самые важные из которых касаются топологии сети и пространства имен. Я считаю, что сеть состоит из одной подсети класса С с одной зоной DNS. В результате, необходимо создать один файл для прямого преобразования имен и один для обратного. Добавить код для работы с несколькими подсетями и зонами (т. е. создать отдельные файлы для каждой) будет несложно.

Вот, вкратце, что мы делаем:

Считываем файл базы данных в хэш хэшей, проверяя при этом данные. Генерируем заголовок. Записываем данные в файл для прямого преобразования (из имен в IP-адреса) и помещаем его под контроль RCS. Записываем данные в файл для обратного преобразования (из IP-адресов в имена) и помещаем его под контроль RCS.

Вот как выглядит пример и получаемые в результате файлы:

use Res;

Sdatafile = "./database";

база данных узлов

Soutputfile = "zone.$$";

временный файл для вывода

$target = "zone.db";

получаемый файл

$revtarget = "rev.db";

получаемый файл для обратного преобразования

$defzone = ".oog.org";

# создаваемая по умолчанию зона

Srecordsep = "-=-\n";

получаем текущую дату в формате YYYYMMDD

@localtime = localtime;

$today = sprintf("%04d%02d%02d",$localtime[5]+1900,

$localtime[4]+l,

$localtime[3]);

имя пользователя, как в NT/2000, так и

Unix $user = ($"0 eq "MSWin32")? $ENV{USERNAME} :

(getpwuid($<))[6]." (".(getpwuid($<))[0].")"; $/ = Srecordsep;

считываем файл базы данных

open(DATA, Sdatafile) or die "Ошибка! Невозможно открыть datafile;$!\n";

while (<DATA>) {

chomp; # удаляем разделитель записей

разбиваем на key!,value"! @record = split /:\s*|\n/m;

$record ={}; # создаем ссылку на пустой хэш

%{$record} = @record;

# заполняем его значениями из ©record

в ищем ошибки в именах узлов

if ($record->{name} =" /["-.a-zA-ZO-9]/) {

warn "!!!! ",$record->{name} .

встретились недопустимые в именах узлов символы, пропускаем.. Дп";

next; }

# ищем ошибки в псевдонимах

if ($record->{aliases} =" /["-.a-zA-ZO-9\s]/) {

warn "!!!! " . $record->{name} .

встретились недопустимые в псевдонимах символы, пропускаем.. .\п";

next; }

# ищем пропущенные адреса unless ($record->{address}) {

warn "!!!! " . $record->{name} .

нет IP-адреса, пропускаем.. Дп"; next; }

# ищем повторяющиеся адреса

if (defined $addrs{$record->{address}}) {

warn "!!!! Повторение IP-адреса:" . $record->{name}.

" & " . $addrs{$record->{address}} . ", пропускаем. . Дп"; next;

>

else {

$addrs{$record->{address}} = $record->{name};

}

$entries{$record->{name}} = Srecord; # добавляем это в хэш хэшей

}

close(DATA);

Sheader = &GenerateHeader;

создаем файл прямого преобразования open(OUTPUT,"> Soutputfile") or

die "Ошибка! Невозможно записать в $outputfile:$!\n": print OUTPUT $header;

foreach my Sentry (sort byaddress keys %entries) { print OUTPUT

"; Владелец -- ",$entries{$_}->{owner},"

(", $entries{$entry}->{department},"):

Sentries{$entry}->{building},"/", Sentries{$entry}->{room), "\n";

tt выводим запись А

printf OUTPUT "%-20s\tIN A %s\n",

Sentries{Sentry}->{name},Sentries{Sentry>->{address};

it выводим записи CNAMES (псевдонимы)

if (defined $entries{$entry}->{aliases}){

foreach my Salias (split(p ',$entries{$entry}->{aliases}))

{

printf OUTPUT "%-20s\tIN CNAME %s\n",Salias,

Sentries{Sentry}->{name}: > }

print OUTPUT "\n"; }

close(OUTPUT);

Rcs->bindir('/usr/local/bin'); my Srcsobj = Rcs->new;

$rcsobj->file($target);

$rcsobj->co('-!');

rename($outputfile,Starget) or

die "Ошибка! Невозможно переименовать

Soutputfile в Starget:$!\n": $rcsobj->ci("-u","-m"."

Преобразовано пользователем Suser в ".scalar(localtime));

ft создаем файл обратного преобразования open(OUTPUT,"> Soutputfile") or

die "Ошибка! Невозможно записать в $outputfile:$!\n"; print OUTPUT Sheader;

foreach my Sentry (sort byaddress keys %entries) { print OUTPUT

"; Владелец-- ",$entries{$entry}->{owner}," (",

Sentries{Sentry}->{department},"): ",

$entries{$entry}->{building},"/",

Sentries{Sentry}->{room},"\n";

printf OUTPUT "%-3d\tIN PTR %s$defzone.\n\n",

(split/\./,$entries{$entry}->{address})[3],

$entnes{$entry}->{name};

clOse(OUTPUT);

$rcsobj->file($revtarget);

$rcsob]->co( '-1');

предполагаем, что целевой файл по крайней

# мере один раз извлекался из репозитория rename($outputfile,Srevtarget) or

die "Ошибка! Невозможно переименовать

Soutputfile в Srevtarget;$!\n";

$rcsobj->ci("-u","-m"."Преобразовано пользователем

$user в ".scalar(localtime));

sub GenerateHeader{ my(Sheader);

if (open(OLDZONE,$target)){ while (<OLDZONE>) {

next unless (/(\d{8}).«serial/); $oldserial = $1; last; }

close(OLDZONE); } else {

Soldserial = "000000"; }

$olddate = substr($oldserial,0,6);

$count = (Solddate == $today) ? substr($oldserial,6,2)+1 : 0;

Sserial = sprintf("%6d%02d",$today,Scount);

$header .= "; файл зоны dns - СОЗДАН $0\п";

Sheader .= "; HE РЕДАКТИРУЙТЕ ВРУЧНУЮ!\n;\n";

Sheader .= "; Преобразован пользователем Suser в ".scalar(localtime)."\n;\n":

П подсчитываем число узлов в каждом отделе foreach Sentry (keys %entries){

$depts{$entries{$entry)->{department}}++; } foreach $dept (keys %depts) {

Sheader .= "; в отделе $dept $depts{$dept} машин.\n";

X

Sheader ,= "; общее число машин: ".scalar(keys %entries)."\nff\n\n";

Sheader .= «"EOH";

@ IN SOA dns.oog.org. hostmaster.oog.org. (

$serial : serial 10800 ; refresh 3600 : retry 604800 ; expire 43200) ; TTL

@ IN NS dns.dog.org

ЕОН

return $header; }

sub byaddress {

@a = split(/\./,$entries{$a}->{address});

@b = split(/\./,$entries{$b}->{address});

($a[0]<=>$b[0]) ||

($a[1]<=>$b[1]) ||

($a[2]<=>$b[2]) ||

($a[3]<=>$b[3]); }

Вот какой файл получается для прямого преобразования (zone.db):

файл зоны dns - СОЗДАН createdns НЕ РЕДАКТИРУЙТЕ ВРУЧНУЮ!

Преобразован пользователем David N. Blank-Edelman (dnb); в Fri May 29 15:46:46 1998

в отделе design 1 машин. в отделе software 1 машин, в отделе IT 2 машин, общее число машин: 4

@ IN SOA dns.oog.org. hostmaster.oog.org. (

1998052900 ; serial 10800 ; refresh 3600 ; retry 604800 ; expire 43200) ; TTL

@ IN NS dns.oog.org.

; Владелец -- Cindy Coltrane (marketing): west/143 bendir IN A 192.168.1.3

ben IN CNAME bendir

bendoodles IN CNAME bendir

; Владелец -- David Davis (software): main/909 shimmer IN A 192.168.1.11

shim IN CNAME shimmer

shimmy IN CNAME shimmer

shimmydoodles IN CNAME shimmer

; Владелец -- Ellen Monk (design): main/1116 Sulawesi IN A 192.168.1.12

sula IN CNAME Sulawesi

su-lee IN CNAME Sulawesi

; Владелец -- Alex Rollins (IT): main/1101 sender IN A 192.168.1.55

sandy IN CNAME sander

micky IN CNAME sander

mickydoo IN CNAME sander

А вот как выглядит файл для обратного преобразования (rev.db):

файл зоны dns - СОЗДАН createdns НЕ РЕДАКТИРУЙТЕ ВРУЧНУЮ!

Преобразован пользователем David N. Blank-Edelman (dnb); в Fri May 29 15:46:46 1998

в отделе design 1 машин, в отделе software 1 машин, в отделе IT 2 машин, общее число машин: 4

@ IN SOA dns.oog.org. hostmaster.oog.org. (

1998052900 ; serial 10800 ; refresh 3600 ; retry 604800 ; expire 43200) ; TTL

@ IN NS dns.oog.org.

; Владелец -- Cindy Coltrane (marketing): west/143 3 IN PTR bendir.oog.org.

; Владелец -- David Davis (software): main/909

11 IN PTR shimmer.oog.org.

; Владелец -- Ellen Monk (design): main/1116

12 IN PTR sulawesi.oog.org.

; Владелец -- Alex Rollins (IT): main/1101 55 IN PTR sander.oog.org.

Этот метод создания файлов открывает перед нами много возможностей. До сих пор мы генерировали файлы, используя содержимое одного текстового файла базы данных. Запись из базы данных считывалась и записывалась в файл, возможно, подвергаясь при этом форматированию. Таким образом, в создаваемые файлы попадали только записи из базы данных.

Иногда бывает полезно, чтобы сценарий добавлял в процессе преобразования свои предопределенные данные. Например, в случае с конфигурационными файлами DNS можно улучшить сценарий преобразования так, чтобы он добавлял записи MX (Mail eXchange), указывающие на центральный почтовый сервер, для каждого узла из базы данных. Простое изменение нескольких строк кода с таких:

К выводим запись А

printf OUTPUT "%-20s\tIN A %s\rT, Sentries{Sentry}->{name},Sentries{Sentry}->{address}:

на следующие:

# выводим запись А

printf OUTPUT "%-20s\tIN A %s\n",

$entries{Sentry}->{name},Sentries{Sentry}->{address};

и выводим запись MX

print OUTPUT " IN MX 10 $mailserver\n";

приведет к тому, что почта, посылаемая на любой из узлов домена, будет направляться на машину $mailserver. Если эта машина настроена так, что может обрабатывать почту для всего домена, то мы задействовали очень важный компонент инфраструктуры (централизованную обработку почты), добавив всего лишь одну строчку кода на Perl.



Массив возвращенный функцией split ()



Таблица 5.1. Массив, возвращенный функцией split ()

Элемент Значение
$record[0] Name
$record[1] Shimmer
$record[2] Address
$record[3] 192.168. 1.11
$rocord[4] Aliases
$record[5] Shim shimmy shimmydoodles
$record[6] Owner
$record[7] David Davis
$record[8] Department
$record[9] Software
$record[10] Building
$record[11] Main
$record[12] Room
$record[13] 909
$record[14] Manufacturer
$record[l5] Sun
$record[16] Model
$record[17] UltraGO

Присмотримся внимательно к содержимому списка. Начиная с элемента $record[0], у нас есть список пар ключ-значение (т. е. ключ=Пате, значение=5183, значение=192.168.1,11\п...), который следует просто присвоить хэшу. После создания хэша можно напечатать нужные нам части.



Внедрение системы контроля исходного кода



Внедрение системы контроля исходного кода

Перед тем как перейти к следующему способу преобразования IP-адресов в имена, хотелось бы добавить к процессу создания файла узлов еще одну хитрую возможность обработки, поскольку один-единственный файл приобретает общесетевое значение. Ошибка в этом файле повлияет на всю сеть. Чтобы обезопасить себя, нужен способ, выполняющий откат изменений, нарушивших файл. Особенно необходимо иметь возможность вернуться назад к предыдущим версиям файла.

Самый элегантный способ создать подобную машину времени - добавить к процессу систему контроля исходного кода. Такой контроль используется разработчиками с целью:

Регистрации всех изменений важных файлов. Предотвращения ситуации, когда несколько человек одновременно изменяют один и тот же файл, тем самым ненамеренно уничтожая действия друг друга. Получить возможность вернуться к предыдущим версиям файла, т. е. отказаться от изменений, вызвавших проблемы.

Подобные средства контроля очень полезны для системного администратора. Проверка ошибок, добавленная в разделе «Проверка ошибок в процессе генерирования файла узлов», поможет справиться лишь с некоторыми опечатками и синтаксическими ошибками, но не спасет от семантических ошибок (таких как удаление важного имени узла, присвоение ошибочного IP-адреса, ошибка в имени узла). В процесс преобразования можно добавить проверку и семантических ошибок, но отловить все возможные погрешности все равно не удастся. Мы уже говорили, что защиты от дураков не существует, потому что они изобретательны.

Можно, наверное, предположить, что было бы лучше применить систему контроля исходного кода к процессу редактирования первоначальной базы данных, но есть две веские причины, по которым очень важно применить ее к данным, получаемым в результате:

Время

Для большого набора данных процесс преобразования может занять некоторое время. Если ваша сеть «упала», и вам необходимо вернуться к предыдущей версии, то необходимость наблюдать, пока Perl сгенерирует нужный файл, вас просто обескуражит (и все это при условии, что вы смогли сразу добраться до Perl).

База данных

Если для хранения данных вы будете использовать настоящую базу данных (а часто это правильный выбор), то может просто не существовать способа применить к ней систему контроля версий. Вероятно, вам придется писать собственные механизмы контроля версий для процесса редактирования базы данных.

В качестве системы контроля исходного кода я выбрал систему контроля версий RCS. У RCS есть несколько возможностей, дружественных к Perl и системному администрированию:

RCS работает на многих платформах. Существуют версии GNU RCS 5.7 для большинства Unix-систем, Windows NT, MacOS и т. д. У нее вполне определенный интерфейс командной строки. Все действия можно выполнить из командной строки даже в операционной системе, где в основном применяется графический интерфейс. Ее очень легко использовать. Небольшой набор команд для выполнения основных операций можно выучить за пять минут (приложение А «Пятиминутное руководство по RCS»). В RCS есть ключевые слова. В текст файлов, находящихся под контролем RCS, можно добавлять магические строки, которые будут автоматически раскрываться. Например, любое вхождение в файл строки $Date: $ будет заменено датой последнего помещения файла в систему RCS. RCS бесплатна. Исходный код GNU-версии RCS распространяется свободно, кроме того, доступны уже скомпилированные версии для большинства систем. Исходный код RCS можно найти на ftp://\ ftp.gnu.org/gnu/rcs.

Если вы никогда не работали с RCS, загляните сначала в приложение А. Впредь будем считать, что вы знакомы с основным набором команд RCS.

Крэйг Фретер (Craig Freter) написал объектно-ориентированный модуль Res, который упрощает применение RCS из Perl. Для этого необходимо:

Загрузить модуль. Указать, где расположены команды RCS. Создать новый объект Res, настроить его в соответствии с файлом, который вы используете. Вызвать необходимые методы объекта (названные в соответствии с командами RCS).

Добавим модуль Res в программу генерации файла узлов, чтобы увидеть, как он работает, и применим другой способ вывода данных. Теперь они будут записываться в определенный файл, а не в стандартный поток вывода STDOUT, как было раньше. Ниже приведен только измененный код. Пропущенные строки, представленные в виде «...», можно найти в предыдущем примере:

$outputfile="hosts.$$";

# временный файл для вывода

$target="hosts";

# где сохранить преобразованные данные

open(OUTPUT,"> Soutputfile") or die

"Невозможно записать в файл

$outputfile:$!\n";

print OUTPUT "»\n\# host file - GENERATED BY $0\n

tt DO NOT EDIT BY HAND! \nft\n"; print OUTPUT "

Converted by $user on ".scalar(localtime)."\nfl\n";

foreach my $dept (keys %depts) {

print OUTPUT "tt number of hosts in the $dept department:

$depts{$dept}.\n"; }

print OUTPUT "tt total number of hosts: ".scalar(keys %entries)."\ne\n\n";

обходим в цикле все узлы и выводим комментарий tt вместе с самой записью

foreach my Sentry (sort byaddress keys %entries) { print OUTPUT

"# Owned by ",$entries{$entry}->{owner},"

(", $entries{$entry}->{department},"): ",

$entries{$entry}->{building},"/".

Sentries{$entry}->{room},"\n";

print OUTPUT

$entries{$entry}->{address},"\t", $entries{$entry}->{name},"

", Sentries{$entry}->{aliases},"\n\n"; }

Close(OUTPUT);

use Res;

путь к RCS

Rcs->bindir(Vusr/local/bin1);

создаем новый RCS-объект

my $rcsobj = Rcs->new;

ft передаем ему имя получаемого файла

$rcsobj->file($target);

tt получаем его из репозитория RCS (он уже должен быть там)

$rcsobj->co('-!');

ft переименовываем вновь созданный файл

rename($outputfile,$target) or

die "Невозможно переименовать Soutputfile в $target:$!\n";

помещаем его в репозиторий RCS

$rcsobj->ci("-u","-m"."Converted by $user on ".scalar(localtime));

В данном примере предполагалось, что целевой файл хотя бы один раз помещался в репозиторий.

Взглянув на фрагмент записей из rlog hosts, можно понять, как действует программа:

revision 1.5

date: 1998/05/19 23:34:16; author: dnb; state: Exp; lines: +1 -1

Converted by David N. Blank-Edelman (dnb) on Tue May 19 19:34:16 1998

revision 1.4

date: 1998/05/19 23:34:05; author: eviltwin; state: Exp; lines: +1 -1

Converted by Divad Knalb-Namlede (eviltwin) on Tue May 19 19:34:05 1998

revision 1.3

date: 1998/05/19 23:33:35; author: dnb; state: Exp; lines: +20 -0

Converted by David N. Blank-Edelman (dnb) on Tue May 19 19:33:16 1998

Из предыдущего примера видно, что между версиями файла нет больших различий (обратите внимание на часть, включающую lines:), зато отслеживаются все изменения, происходящие при создании файла. При необходимости узнать, что именно произошло, достаточно воспользоваться командой rcsdiff. В крайнем случае, всегда можно вернуться к предыдущим версиям, если какие-либо изменения приведут сеть в неработоспособное состояние.



Вы уже приняли религию «Базы данных для системного администрирования»?



Вы уже приняли религию «Базы данных для системного администрирования»?

В главе 3 «Учетные записи пользователей» я предложил использовать отдельную базу данных для хранения информации об учетных записях. Те же аргументы вдвойне справедливы для данных об узлах в сети. В этой главе будет показано, как можно работать даже с самой простой базой данных в формате плоского файла, чтобы получить впечатляющие результаты, нужные всем обсуждаемым службам. Для сайтов большего размера следует использовать «настоящую» базу данных. Пример получаемых данных можно найти в конце раздела «Улучшение полученного файла узлов» этой главы.

Использование базы данных для узлов в сети выгодно по целому-ряду причин. В этом случае вносить изменения нужно только в один файл или источник данных. Вносите изменения, запускайте определенные сценарии и, пожалуйста, у вас есть конфигурационные файлы для множества служб. В таких конфигурационных файлах почти наверняка не будет мелких синтаксических ошибок (скажем, пропущенных точек с запятыми или символов комментариев), поскольку их не коснулись человеческие руки. Если правильно написать код, то практически все остальные ошибки можно обнаружить на стадии обработки данных анализатором (parser).

Если вы еще не осознали всей мудрости этого «лучшего подхода», к концу главы сами в этом убедитесь.



Windowsслужба имен Интернета (WINS)



Windows-служба имен Интернета (WINS)

Когда в Microsoft стали использовать свой патентованный сетевой протокол NetBIOS поверх TCP/IP (NetBT), возникла необходимость решать проблему соответствия IP-адресов и имен узлов. Первым решением стало использование файла Imhosts, спроектированного по аналогии со стандартным файлом узлов. Но это было быстро дополнено NIS-подобным механизмом. В NT 3.5 появилась централизованная схема под названием Windows-служба имен Интернета (Windows Internet Name Service, или WINS). WINS несколько отличается от NIS:

WINS специализируется на распространении информации о соответствии имен узлов IP-адресам. В отличие от NIS, эта служба не применяется для централизованного распространения другой информации (например, паролей, карты портов и групп пользователей). WINS-сервера получают большую часть из распространяемой информации от предварительно настроенных клиентов (такую информацию можно предварительно загрузить). После получения IP-адреса либо вручную, либо через протокол динамической конфигурации узла (Dynamic Host Configuration Protocol, DHCP) WINS-клиенты ответственны за регистрацию и перерегистрацию своей информации. В этом состоит различие с NIS, там клиенты запрашивают информацию у сервера и, за исключением паролей, не обновляют на нем информацию.

WINS, как и NIS, позволяет иметь несколько серверов, повышающих надежность и разделяющих загрузку, по принципу«тяни-толкай». В Windows 2000 WINS вышла из употребления (читай «от нее избавились»), вместо нее теперь используется служба динамических доменных имен (Dynamic Domain Name Service), являющаяся расширением DNS-системы, о которой мы очень скоро поговорим еще.

Учитывая, что WINS больше не существует, мы не будем приводить примеров для работы с ней. В настоящее время работа с WINS напрямую из Perl поддерживается очень слабо. Я не знаю о существовании модулей, созданных специально для работы с WINS. В этом случае лучше всего вызывать некоторые утилиты, работающие в командной строке из Windows NT Server Resource Kit, например WINSCHK и WINSCL.