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

         

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




Модуль

Идентификатор на CPAN

Версия

Getopt : : Std (входит в состав Perl)   1.01
Digest: :MD5

GAAS 2.09
Net: :DNS MFUHR 0.12
FreezeThaw

ILYAZ 0.3
File : : Find (входит в состав Perl)     
Net: :SNMP

DTOWN 3.01
SNMP GSM 3.10
Net : : Ping (входит в состав Perl) RMOSE 2.02
Net: :Pcap

TIMPOTTER 0.03
Net: :PcapUtils

TIMPOTTER 0.01
NetPacket TIMPOTTER 0.01
Term: :ReadKe> KJALB 2.14



Обращаем внимание на неожиданные или несанкционированные изменения


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

файлами /bin/login, msgina.dll или Finder, то вы, заметив это, будете настолько счастливы, что простите все предыдущие ложные тревоги.

Изменения локальной файловой системы

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

(например /etc/passwd или msgina.dll). Изменения, внесенные в эти файлы без ведома администратора, часто являются признаками вмешательства злоумышленника. В сети существует ряд довольно сложных инструментов, которые устанавливают троянские версии важных файлов и заметают следы. Это самые злобные изменения, которые можно обнаружить. С другой стороны, иногда просто полезно знать, что важные файлы изменились (особенно если одну и ту же систему администрируют несколько человек). Технологии, которые мы рассмотрим, будут

одинаково хорошо работать в обоих случаях.

Самый простой способ выяснить, был ли файл изменен - использовать функции stat() и lstat(). Эти функции принимают имя файла или файловый дескриптор и возвращают массив с информацией об этом файле. Единственное различие между этими двумя функциями проявляется в операционных системах, подобных Unix, поддерживающих символические ссылки. В таких случаях 1 stat () применяется для получения информации о файле, на который указывает ссылка, а не о самой символической ссылке. На всех остальных операционных системах информация, возвращаемая функцией lstat(), будет совпадать с информацией, возвращаемой функцией stat().




Использовать stat() или lstat() очень просто:

@information = stat("filename");

Как сказано в главе 3 «Учетные записи пользователей», можно также применять модуль File: :Stat Тома Кристиансена, чтобы получить эту же информацию, используя объектно-ориентированный синтаксис.

Информация, возвращаемая функциями stat() и lstat(), зависит от операционной системы. stat() и lstat() происходят от системных вызовов в Unix, поэтому Perl-документация по этим функциям ссылается на значения, возвращаемые в Unix. Можно посмотреть (табл. 10.1). как эти значения соотносятся с тем, что возвращается функцией в Windows NT/2000 и MacOS. В первых двух столбцах приведены порядковый номер поля и его описание для систем Unix.

Таблица 10.1. Сравнение значений, возвращаемых функцией stat( )







Описание поля в Unix



Действительно в

NT/2000



Действительно в MacOS

0

Номер устройства файловой системы

Да (порядковый но-

мер диска)

Да (но является vRefNum)

1

Inode

Нет (всегда 0)

Да (но filelD/dirlD)

2

Режим файла (тип и права)

Да

Да (но 777 для каталогов и приложений, 666 для незаблокированных документов, 444 для заблокированных документов)

3

Количество (жестких) ссылок на файл

Да (для NTFS)

Нет (всегда 1)

4

Численный идентификатор владельца файла

Нет (всегда 0)

Нет (всегда 0)

5

Численный идентификатор группы владельца файла

Нет (всегда 0)

Нет (всегда 0)

6

Идентификатор устройства (только для специальных файлов)

Да (порядковый номер диска)

Нет (всегда null)

7

Размер файла в байтах

Да (но не включает размер каких-либо альтернативных

потоков данных)

Да (но возвращает только размер данных)

8

Время последнего доступа относительно начала эпохи

Да

Да (только эпоха начинается на 66 лет раньше, чем в Unix, то есть 1/1/1904, и значение то же, что и для поля №9) "

9

Время последней модификации относительно начала эпохи

Да

Да (только эпоха начинается 1/1/1904 и значение то же, что и для поля №8)

10

Время последнего изменения inode относительно начала эпохи

Да (но время создания файла)

Да (только эпоха начинается 1/1/1904, и это время создания файла)

11

Предпочтительный размер блока для ввода/вывода

Нет (всегда null)

Да

12

Количество занятых блоков

Нет (всегда null)

Да
<


Для возвращения атрибутов, специфичных для операционной системы, в других He-Unix-версиях Perl помимо stat() и lstat() используются специальные функции. Рассказ о таких функциях, как Perl::Getr41oInf u() и Win32: :FileSecunу : ujt.(), можно найти в главе 2 «Файловые системы».

После того как с помощью stat() для файла будут получены значения, на следующем шаге надо будет сравнить «интересные» значения с уже известными. Если они изменились, значит, изменилось и что-то в этом файле. Ниже приведена программа, которая генерирует строку значений ista l () и проверяет для файлов некоторые из этих значений. Мы намеренно исключили 8-е поле (время последнего доступа), потому что оно меняется при каждом прочтении файла.

Программа принимает либо аргумент -р filename, чтобы вывести значения lstat() для заданного файла, либо аргумент -с filename, чтобы проверить значения lstat() для всех файлов, перечисленных в filename.

use Getopt::Std;

используем это для создания более симпатичного вывода позже в &pnntchanged()

@statnames = qw(dev ino mode nlink uid gid rdev size mtime ctime blksize blocks);

getopt('p:c:');

die "Использование: $0 [-p <filename>|-c <filena:iie>]\n" unless ($opt_p or $opt_c);

if ($opt_p)(

die "Невозможно получить информацию о файле $opt._p:

unless (-с $opt_p): print $opt_p."|",]OinC |',(lstat($opt_p))[0..7,9..12]),"\n":

exit:

if ($opt_c){

oper.(CFILE,$opt_c) or

die "Невозможно открыть файл $opt_c:$!\": while(<CFILE>){ cho;:!p:

ssavecstats = spl-'('\:' ); die Неверное количество полей в строке, начинающейся с

$savedstats[C']\'T jniess (Sttsaveustits == 12): s<,i.r rentstats = (Isratv $savedstat3[0]) )[0. 7,9. 12]

}

close(CFILE);

}

sub printchanged{

my($saved. $curre!it)= ®_:

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

prin: shift §{$saved}.":\n":

for (my $i=0; $1 < $#{$saved};$!++){

if ($saved->[$i] ne $current->[$i]){



print "\t".$statnames[$i]." is now ".$current->[$i];

print " (should be ".$saved->[$i].")\n":

} }

Для использования этой программы можно набрать checkfile -p /etc/passwd » checksumfile. В файле checksumfile теперь будет храниться строка, которая выглядит так:

/etc/passwd|1792|11427]33060)1|0|0|24959|607|921016509|921016509|8192|2

Этот шаг нужно повторить для каждого файла, за которым мы наблюдаем. Затем вызов сценария с аргументом checkfile -с checksumfile будет сообщать обо всех изменениях. Например, если я удалю один символ из /etc/passwd, сценарию это не понравится, и он выведет такое сообщение:

/etc/passwd:

size is now 606 (should be 607)

mtime is now 921020731 (should be 921016509)

сtime is now 921020731 (should be 921016509)

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

if ("®savedstats[1..12]' ne "@currentstats"):

Perl автоматически преобразовывает список в строку, склеивая элементы списка через пробел:

joint" ",ssavedstats[1. . 12]))

и затем уже сравнивает получившиеся строки. Этот прием хорошо работает для коротких списков, в которых имеет значение порядок и количество элементов. В большинстве других случаев необходимо использовать итеративный подход или хэши, как описано в списках часто задаваемых вопросов perlfaq, входящих в состав Perl.

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

Обнаружение изменений в данных - это одна из сильных сторон алгоритмов, известных как криптографические хэш-функции («message-di-gest algorithms»). Вот как Рон Райвест (Ron Rivest) описывает алгоритм «RSA Data Security, Inc. MD5 Message-Digest Algorithm» в RFC1321:



Алгоритм на вводе принимает сообщение произвольной длины и создает подпись (message digest или fingerprint) длиной 128 бит. Считается, что просто невозможно создать два сообщения, у которых совпадали бы подписи; также невозможно создать сообщение, подпись которого совпадала бы с заранее заданной.

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

этой чудесной возможностью из Perl - применить модуль LUgos, : : МУ:> из семейства модулей Digest.

Использовать модуль Digest:: MD5 просто. Нужно создать объект, добавить в него данные при помощи методов add () miHaridfile(), а затем попросить модуль создать подпись.

Можно сделать нечто подобное для подсчета подписи MD5 для файла паролей в Unix:

use Digest: :MD5 qw(rnd5); $md5 = new Digest::MD5:

open(PASSWD. "/Gtc/f.'asswd") or die "Невозмонсть открыть pass,vd$' ":

acdf :Ie(PASSWD):

ciose(PASSlvD)

prit 3r!d5->hexd:g-2s: . "v "",

В документации по Digest: :MD5 сказано, что для создания более компактных программ можно связывать несколько методов вместе. Так, предыдущую программу можно переписать:

Файлы perlfaql. pod, perlfaq2.pod ... perlfaq[N].pod.

use Digest::MD5 qw(md5);

open(PASSWD. Vei-c/pabswri") or 'I:e "Ненот.' print Digest: :MD5--'neiA->addfi](](PASSWD) close(PASSWD):

Обе программы выводят следующее:

a6f905e6h45a65a7e03dOS09448b501c

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

335679с4с97а381523034331a06df3e7

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

use Getopt::Std;

use Digest::MD5 qw(md5);

@statnames =

qw(dev ino mode nlink uid gid rdev size mtime ctinie blksize blocks md5):



getopt('p:c:');

die "Использование: 0 [-p <filename>|-c <fileriame>]\n"

unless ($opt_p or $opt_c):

if ($opt^.p){

die "Невозможно получить информацию о файле $opt_p:$'\n"

unless (e $opt_p);

open(F.$opt_p) or die "Невозможно открыть $opt_p:$'\r";

$d.igest = Digest: ;MD5->new-^addfile(F)->hexaigest:

ciose(F):

print $opt__p, "|", ]oin

"|$digest", "\n":

exit:

}

if ($ppt_c){

open(CFILE.$opt_c) or

die "Невозможно открыть Файл;.

wnilc (<CFILE>){

c'-.o^p:

ssavedstats = spli r(

far,, rp'.tstats = (lstat($s;ivenstars[OJ))[0 . Л9..121:

doSi:( h )

&pr unchanged (\3savudstats. Vicur: и its: a; .1)

if ("«?savedstars[1 13]"

close(CFILE):

}

sub printcharigcd {

my($saved,$cnrrent)= ®_:

print shift @{$saved).":\n";

for (my $i=0; $1 <= $(({$saved}; $!++){

if ($saved->[$i] ne $current->[$i]){

print " P\$statnames[$i]." is now ", $current->[$i ]:

print " (".$saved->[$i].")\n";

}



Изменения сетевых служб



Мы узнали, как обнаружить изменения в локальных файловых системах. Как насчет того, чтобы заметить изменения на других машинах или в службах, ими поддерживаемых? Мы уже видели способы запроса NIS и DNS в главе 5 «Службы имен TCP/IP». Не должна вызвать затруднений проверка изменений в повторяющихся запросах к этим службам. Например, можно притвориться вторичным сервером и запросить копию данных (т. е. выполнить зонную пересылку) с сервера для определенного домена, если, конечно, DNS-сервер настроен так, что позволит сделать это:

use Net::DNS;

принимает два аргумента в командной строке: первый - сервер

 имен, к которому посылается запрос, а второй - интересующий

и нас доме"

$server = new Net::DNS::Resolver.

$server-:^.ar;eservers($ARGV[C]);

ur;n+ 3 ' OERR 'Выполняется ''еррдоча.

i:c $sorver-.'Or rorst гц-.д jr,:ess (de'ired ьу(У-(-}\

for Srecord (@zone){

$record-'>pr int;



}

Объединим эту идею с MD5. Вместо того чтобы получать информацию о зоне, давайте просто сгенерируем для нее подпись:

use Net::DNS:

use FreezeTnaw qiv{f reeze!;

use Digest::MD5 qw(md5):

Sserver = new Net: :DNS::Resolver:

$server->naineservers($ARGV[0]):

print STDERR "Выполняется передача,..";

@zone = $server->axfr($ARGV[1]);

die $server->errorstring unless (defined @zone);

print STDERR "готово.\n";

$zone = join('',sort map(freeze($_),@zone));

print "MD5 fingerprint for this zone transfer is: ";

print Digest::MD5->new->add($zone)->hexdigest,"\n";

MD5 работает со скалярными данными (сообщение), но не со структурами типа списка кэшей, как @zone. Вот почему нужна такая строчка:

Szone = join(",sort map(freeze($_),@zone));

Для преобразования каждой записи из структуры данных @zone в обычные строки воспользуемся модулем FreezeThaw, который мы уже видели в главе 9 «Журналы». Перед тем как записи будут склеены в одно большое скалярное значение, они будут отсортированы. Сортировка позволяет проигнорировать порядок, в котором возвращались записи при пересылке зоны.

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

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

Что если кто-то подделает базу данных MD5 подписей и подставит действительные подписи под поддельные троянские файлы или изменения служб?

Что если кто-то подделает ваш сценарий таким образом, что он будет только создавать видимость проверки подписей со значениями из базы данных?

Что если кто-то сделает что-нибудь с модулем MD5 на вашей системе?

Это уже, конечно, предел паранойи, но что если кто-то сделает что-то с самим исполняемым файлом Perl, одной из его разделяемых библиотек или самим ядром операционной системы?

Обычные ответы на эти вопросы (какими бы они ни были неудовлетворительными): храните хорошие копии всего, что имеет отношение к этому процессу (базы данных подписей, модули, Perl и т. д.) на устройствах, к которым разрешен доступ только для чтения.

Эта головоломка - еще одна иллюстрация бесконечности безопасности.

Всегда можно найти что-то, чего можно опасаться.






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


Хорошему ночному сторожу нужно больше, чем просто возможность наблюдения за изменениями. Он также должен иметь возможность реагировать на подозрительные действия или обстоятельства. Обязательно нужно сообщить кому-то о дыре в заборе или необъяснимых ударах среди ночи. Мы можем написать программы, которые возьмут на себя эту роль.

Локальные признаки опасности

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

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

сделали, кого еще атаковали, что вы сделали в ответ и т. д.

Заманчиво было бы вернуться к обычной жизни и забыть про взлом. Если вы не подвергнетесь этому соблазну, то позже поймете, что инцидент научил вас кое-чему, и что вы не просто потеряли время и силы. Принцип Ницше - «то, что вас не убивает, делает вас сильнее» - часто справедлив и в системном администрировании.

Например, очень часто взломщики, а особенно менее опытные, пытаются замести следы, создавая «скрытые» каталоги для хранения данных. В Unix и Linux они помещают свои программы и вывод подслушивающих программ в каталоги с именами «...» (точка точка точка), (точка пробел) или «Mail» (пробел Mail). Такие имена, скорее всего, останутся без внимания при беглом просмотре вывода команды Is.

При помощи инструментов, о которых мы узнали в главе 2, можно легко написать программу для поиска таких имен. Ниже приведена программа, использующая модуль File: :Find (вызываемый find.pl) для

поиска «ненормальных» имен каталогов.

require "find.pl";

Обходим нужные файловые системы




&find('.');

sub wanted {

(-d $_) and  каталог

$_ ne "." and $_ ne ".." and tt не . и не ..

(/["-.a-zA-ZO-9+.:;_"$»()]/ or

содержит "плохой" символ

/~V {3, }/ or ft или начинается как минимум с ipe>

точек

/"-/) and

или начинается с дефиса

print......&nice($name)." '\n";

}

печатаем "хорошую" версию имени каталога, то есть, не  раскрывая управляющие символы. Эта подпрограмма -- лишь

 немного измененная версия &unctrl() из dumpvar.pl sub nice {

my($name) = $_[0];

$name =' s/([\001-\037\177])/'"'.pack('с'.ord($1)"64)/eg:

$name;

}

Помните врезку «Регулярные выражения» из главы 9? Подобные программы, тщательно анализирующие файловую систему, - еще один пример, где высказанная в главе 9 мысль справедлива. Эффективность таких программ зачастую зависит от качества и количества используемых в них регулярных выражений. Слишком мало регулярных выражений - и вы пропустите то, что хотели найти. Слишком много регулярных выражений или они неэффективны - и ваша программа будет выполняться чрезмерно долго и захватит непомерное количество ресурсов. Если использовать слишком общие регулярные выражения - будет найдено много ложных совпадений. Здесь тонкий баланс.



Поиск проблемных образцов



Теперь воспользуемся тем, что мы узнали в главе 9 и перейдем дальше. Только что мы говорили о поиске подозрительных объектов; давайте теперь рассмотрим образцы (patterns), которые могут быть признаками подозрительной активности. Покажем это на примере программы, выполняющей примитивный анализ журналов в поисках потенциальных взломов.

Пример строится на предположении, что большинство пользователей, удаленно регистрирующихся в системе, делают это из одного и того же места или нескольких мест. Обычно они регистрируются либо с одной машины, либо используя адреса из диапазона, принадлежащего одному и тому же провайдеру. Если вы обнаружите, что пользователь регистрировался из нескольких доменов, это верный признак того, что данная учетная запись была «взломана» и ее пароль стал доступен многим. Очевидно, что такое предположение не справедливо для постоянно перемещающихся пользователей, но если вы обнаружите, что пользователь регистрировался сначала из Бразилии, а потом из Финляндии с интервалом в два часа, то это достаточный повод заподозрить что-то неладное.



Рассмотрим программу, реализующую поиск таких признаков. Сама программа написана для Unix, но демонстрируемые в ней приемы не зависят от платформы. Во-первых, это встроенная документация.

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

программой:

sub usage {

print «"EOU"

lastcheck - проверяет вывод команды last,

ИСПОЛЬЗОВАНИЕ: lastchecK [args]. где вместо может быть:

-i: для IP-адресов, считан-^ сеть класса С одним 'доменом

-л: помощь (это сообщение)

/usr/ucb/last

exit:

Сначала мы анализируем аргументы из командной строки, просматриваются аргументы программы и соответствующим образом устанавливается $ор1_<буква_фла!а>. Двоеточие после буквы

говорит о том, что этот параметр принимается как аргумент:

use Getopt::Std: ft сгиндарт.ньи: процессор

&usage if (defined $opt_h):

it допустимое количество уникальных

Smaxdomains = (defined $opt_ni) 9 Sopt.r : 3:

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

главе:

Slastex = (defined $opt_l) ? $opt_l : "/usr/ucb/last":

open(LAST,"$lastex|") [| die "Невозможно выполнить программу Slastex:$!\n";

Перед тем как двигаться дальше, давайте взглянем на хэш списков, с помощью которого программа обрабатывает данные, полученные от last. Ключами этого хэша являются имена пользователей, а значениями - ссылки на список уникальных доменов, с которых регистрировался пользователь.

К примеру, запись может выглядеть так:

Suserinfo { laf } = [ 'ccs.neu.edu', 'xerox,com', 'foobar.edu' ]

Эта запись говорит о том, что пользователь laf регистрировался с доменов ccs.neu.edu, xerox.com и foobar.edu.



Начинаем мы с того, что обходим в цикле вывод команды last. На нашей системе он выглядит примерно так:

Cindy pts/10 sinai.ccs.neu.ea Fri Mar 27 13:51 still logged in

michael pts/3 regulus. ccs. neu Fn Mar 27 13:51 still logged I"'

david pts/5 fruity-pebbles.с Fri Mar 27 13:48 still logged in

deborah pts/5 grape-nuts.ccs.n Fri Mar 27 11:43 - 11:53 (00:09)

barbara pts/3 152,148.23,66 Fri Mar 27 10:48 - 13:20 (02:31)

Jerry pts/3 nat16. asoer-tec с Fri Mar 27 09:24 - 09:26 (00:01)

Заметьте, что имена узлов (в 3-й колонке) в выводе команды last усечены. В главе 9 мы уже говорили об ограничениях на длину имени узла, но до сих пор мы обходили стороной это препятствие. Когда мы попробуем заполнить нашу структуру данных, проблемы станут очевидными.

Раньше в цикле while мы пытались пропустить строчки, содержащие данные, которые нас не интересуют. Как правило, проверка особых случаев в самом начале цикла до какой-либо обработки данных (на-

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

while (<LAST>){

 игнорируем специальных погьзонателей

next if /~rePoot\s]"shutdown\s|~flp\s/:

 если использовался ключ -и д/'.я определения конкретного

 пользователя, пропускаем все записи, не относящиеся к

 нему (имя сохраняется в $opt_u функцией getopts)

next if (defined $opt_u && !/"$opt_u\s/);

 игнорируем вход с консоли X

next if /:0\s+:0/;

# ищем имя пользователя, терминал и имя удаленного узла

($user, $tty,$host) = split;

 игнорируем, если запись в журнале имеет "плохое" имя

 пользователя

next if (length($user) < 2);

 игнорируем, если для данного имени нет информации о домене

next if $host !" Д./;

 ищем доменное имя узла (см. приведенное ниже объяснение)

$dn = &domain($host);

 игнорируем, если доменное имя фиктивное

next if (length ($dn) < 2);



  игнорируем эту строку, если она находится в домене,

 заданном ключом -f

next if (defined $opt_f && ($dn =' /~$opt_f/));

 если мы не встречали раньше имя этого пользователя,

 просто создаем список доменов для этого пользователя и

 сохраняем эту информацию в хэше списков

unless (exists $userinfo{$user}){

$userinfo{$user} = [$dn];

 в противном случае нам придется нелегко:

 см. приведенное ниже объяснение

else {

&AddToIfo($user.Sdr):

closed-AST).

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

Не все имена узлов из журналов будут именами. Это вполне может быть и IP-адрес. В этом случае, если пользователь устанавливает ключ -г, мы полагаем, что любой получаемый нами IP-адрес - это

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

Как говорилось раньше, имена узлов могут быть усечены. Это приводит к тому, что мы имеем дело с неполными записями, подобными grape-nuts, ccs. n и nat16. aspentec.c. Это не так страшно, как кажется, потому что полностью определенное имя домена в журнале каждый раз будет усекаться на одном и том же месте. В подпрограмме &AddToInfo() мы попробуем сделать все возможное, чтобы справиться с этим ограничением. Но об этом чуть позже.



А пока вернемся к программе:

  принимаем полностью определенное имя домена и пытаемся

 определить домен

sub domain{

 ищем IP-адреса

if ($„[0] =' /-\d+\.\d+v\d+v\d+$/) {

 если пользователь не указал ключ -i. просто

 возвращаем IP-адрес как есть

unless (defined $opt_i){

return $_[0];

}

 иначе возвращаем все. кроме последнего октета

else {

$_[0] =~ /(.-)\.\d+$/:

return $1;

(

}

 переводим все в нижний регистр, чтобы потом было

# проще и быстрее обрабатывать информации!

S. [.и] = 1с($_[С]);

}

}

Следующая очень короткая подпрограмма заключает в себе самую сложную часть программы. Подпрограмма &AodToTr

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

В нашем случае было бы неплохо, если бы все эти имена доменов (читались бы и сохранялись бы в массиве уникальных имен доменов для пользователя как одно имя:

ccs.neu.edu

ccs.neu.ed

ccs. n

Решая, является ли имя домена уникальным, необходимо проверить три вещи:

1. Совпадает ли имя домена полностью с чем-нибудь, что уже сохранено для этого пользователя?

2. Является ли это имя домена подстрокой уже сохраненных данных?

3. Являются ли подстрокой проверяемого имени домена сохраненные данные?

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

Если же не справедлив ни один из этих случаев, то необходимо сохранить новую запись. Посмотрим сначала на код, а потом обсудим, как он работает:

цио AodTolPfc!

 проверка 1-го и 2-го случаев: есть л/ полное или



и частичное совпадение9

renirn If (1ndOvC$ $п;гИ -- IV

и проверка 3-го случая, го есть. явлй^:.;ч ,-;,, :-с„отоокоп

# сохраненные данные

if (index($un. $_.) > -1){

return:

}

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

push @{$userinfo{$user}}, $dn:

}

Конструкция @{$userinfo{$user}} возвращает список доменов, сохраненных для этого пользователя. Мы обходим в цикле все элементы из этого списка, проверяя, можно ли найти среди них $dn. Если можно, то

мы выходим из подпрограммы, т. к. эквивалентная подстрока уже сохранена.

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

Поскольку это не вредит, замена производится и при точном совпадении. Мы переписываем запись, используя специальное свойство операторов fо г и fо reach в Perl. Присваивая значение переменной $_ в середине цикла for, Perl в действительности присваивает значение текущему элементу списка. Переменная цикла становится псевдонимом для переменной списка. После того как мы поменяли местами значения, можно выходить из подпрограммы. Если были пройдены все три проверки, то

в последней строке к списку доменов для пользователя добавляется рассматриваемое имя домена.

Это все, что касается «кровавых» деталей просмотра файла и создания структуры данных. Чтобы завершить эту программу, рассмотрим всех найденных пользователей и проверим, со скольких доменов они регистрировались (т. е. выясним длину сохраненного для каждого из них списка). По тем записям, для которых найдено больше доменов, чем можно, мы выводим полный список:

for (sort keys %usermfo){

if ($#{$jsenr-fo{$J} > $."iaxaora:"s)!

}

РГЩГ "V:' Протокол SNMH

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

38.254.131

bj.edu

ccs.neu.ed

dac.neu.ed

hials.no

ipt. a

tntl.bosl

tntl.bost

tntl. dia

tnt2.bos

tntS.bos

tnt4.bo

toronto4.di

Некоторые из этих записей выглядят нормально для пользователя, живущего в Бостоне. Однако запись toronto4.di выглядит несколько подозрительной, а сайт hials.no вообще находится в Норвегии. Схвачены с поличным!

Программу можно усовершенствовать, добавив проверку времени или сравнение с другими журналами, например, полученных при помощи tcpwrappers. Но как видите, поиск шаблонов часто важен сам по себе.






Опасность на проводе


SNMP хорош для активного наблюдения (а также в некоторых предусматривающих реакцию ситуациях мониторинга, когда используют.'5'

SNMP-прерывания (traps)), но он не всегда помогает, если происходит что-то незапланированное, например авария в сети. В таких случаях, возможно, придется наблюдать за сетью другими способами, которые не охватываются доступными SNMP переменными.

Perl спасает положение

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

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

Последний столбец вывода netstat говорил о том, что с внешним миром действительно было установлено много соединений. Большой неприятностью было состояние этих соединений. Вместо того чтобы выглядеть примерно так:

tcp 0 0 n-ailhub.3322 mail.nel.aone. nc. gui* р ESTABLISHED

tcp О О rnail'iub.3320 cdunet.edunet.dk.smtp CLOSE., WAIT

tcp 0 0 man 1-н,!). '723 k-aken. -wret, wne. sr',p ESTABLISHED

tcp 0 0 mail hub. 1709 pi over. net. D' idg. silt.:: CLOSE_WAIT

они больше напоминали следующее:




tcp 0 0 TiaiihuD. 3322 nail. mel. aone ne. sr.tp Si'N_PGiyD

tcp 0 0 r!'.ailnuD. 3320 ed ,riet. edunc-т.. gk. smp Sr'N_RCVD

tec 0 0 railnub 1723 гтаке^, nvnet .v,ne. sr^D SYN RCVD

tcp 0 0 pailf'oO. 1709 olover.ret or :dc;. s:"n; CLOSE wAI~

На первый взгляд, это было похоже на классическую атаку типа «отказ от обслуживания» (Denial of Service), называемую SYN Flood, или атакой SYH-ACK. Давайте отвлечемся на некоторое время и поговорим о  том, как работает протокол TCP/IP, чтобы понять, что собой представляют эти атаки.

Каждое TCP/IP-соединение начинается с «рукопожатия» между участниками. Это позволяет и инициатору, и получателю сообщить о своей готовности к беседе. Первый шаг предпринимает инициатор соединения. посылая получателю пакет SYN (от SYNchronize - синхронизировать). Если получатель готов к «общению», он посылает в ответ пакет 6Y', >, подтверждение (от ACKnowledgment) запроса, и записывает в таблице отложенных соединений, что должна начаться беседа. Инициатор отвечает на пакет SYM-ACK пакетом АСК, подтверждая, что пакет '-. <'1-М>: был получен. При получении пакета АСК получатель удаляет запись из таблицы, и начинается передача данных.

По крайней мере, именно так все и должно происходить. В случае с атакой SYN Flood, злоумышленник посылает на машину лавину пакетов;'. ',. часто подделывая при этом адрес источника. Ничего не подозревающая машина посылает по поддельным адресам пакеты SYN-АСК и добавляет запись в таблицу ожидающих соединений для каждого полученного пакета SYN. Эти записи остаются в таблице до тех пор, пока операционная система не признает их устаревшими по прошествии определенного вре -мени. Если было послано достаточно много пакетов, таблица ожидающих запросов переполнится, и все попытки установить вполне законное соединение завершатся неудачей. А это приведет к появлению описанных мною симптомов и подобному выводу netstat.

Но в выводе команды netstat была одна аномалия, которая ставила под сомнение мой диагноз - разнообразие узлов в таблице. Возможно, что кто-то обладает программой с отличными способностями к подделкам, но обычно соединения устанавливаются с меньшего числа фальшивых узлов. Кроме того, многие из этих узлов казались настоящими. Ничего не прояснили и даже ухудшили ситуацию некоторые выполненные мною проверки. Попытки выполнить команды ping или traceroute для случайно выбранных узлов из списка, предоставленного командой net stat, иногда завершались успешно, а иногда нет. Мне не хватало данных. Надо было лучше разобраться с соединениями по этим удаленным узлам. Тут мне на помощь пришел Perl.



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

На этот раз задача свелась к одному вопросу: могу ли я добраться до уз лов, пытающихся связаться со мной? Для поиска узлов, пытающихся связаться с моей машиной, я воспользовался программой clog Брайена Митчела (Brian Mitchell), которую можно найти на ftp://coast.cx.pn> due.edu/pub/mirrors/ftp.saturn.net/clog. Для прослушивания сети в поисках запросов TCP-соединений, т. е. пакетов SYN, clog использует библиотеку ИЬрсарот Lawrence Berkeley National Laboratory's Network Re

search Group. Эту же библиотеку использует и эффективная программа наблюдения за сетью tcpdump. Библиотека libpcap с ftp://ftp.ee.lbl.gov/lib pcap.tar.Z работает и для машине Linux. Перенесенная версия libpcap для NT/2000 доступна на http://netgroup-serv.polito.it/windump/ или http:// www.ntop.org/libpcap.html, но хотелось бы также увидеть версию и для MacOS.

clog

сообщает о пакетах SYN таким образом:

Маг 02 11:21|192.168.1.51|1074|192,168.1,104(113 Маг 02 11:21|192.168.1.51|1094|192.168.1.104|23

Из примера видно, что получено два запроса на соединение от машины 192.168.1.51 к 192.168.1.104. Первый- это попытка соединиться с портом 113 (ident), а второй - с портом 23 (telnet).

Программа clog помогла мне выяснить, какие узлы пытались установить соединение со мной. Но мне надо было знать, могу ли я до них добраться. Эта задача выпала на долю программы fping Роланда Дж. Шемерса III (Roland J. Schemers III). Программа fping, которую можно найти на http://www.stanford.edu/~schemers/docs/fping/fping.html, - это быстрая и шикарная версия программы ping для тестирования работоспособности сети в Unix и его вариантах. Воспользовавшись этими двумя внешними программами, получаем маленькую программу на Perl:



Sclogex = "/usr/local/bin/clog"; # местоположение/ключи для

clog Sfpingex = "/us'r/local/bin/fping -r1"; # местоположение/ключи для fping

Slocalnet = "192.168.1"; ft префикс локальной сети

open CLOG, "$clogex|" or die "Невозможно запустить ciog:$!\n"; while(<CLOG>){

($date,$onghost.$ongport, Sdesthost,Sdestport) = split(/\|/);

next if (Songhost =" /~$localnet/);

next if (exists $cache{$onghost});

print 'Sfpingex Sorighost';

$cache{$orighost}=l; }

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

На этот раз, как и в последнем примере, мы используем некоторое кэширование. Мы - добропорядочные жители сети и не собираемся закидывать внешние машины множеством пакетов ping, так что мы следим за тем, к каким узлам мы уже обращались с запросами. Флаг ~rl у fping служит для ограничения количества попыток обращения к узлу программой fping (по умолчанию предпринимается три попытки).

Эту программу необходимо выполнять с повышенными привилегиями, т. к. и программе clog, и программе fping нужен привилегированный доступ к сетевому интерфейсу компьютера. Вывод этой программы выглядит так:

199.174,175.99 is unreacrable

128,148.157 143 is unreachablo

204.241.60.5 is alive

199.2.26.116 is unreachable

199.172.62.5 is unreacnable

130.111.39.100 is alive

207.70.7.25 is unreachabie

198.214.63.11 is alive

129.186.1.10 is alive

Очевидно, что здесь творится нечто подозрительное. С чего вдруг половина узлов доступна, а половина - нет? Перед тем как ответить на этот вопрос, давайте посмотрим, что можно сделать для улучшения этой программы. Первый шаг - избавиться от зависимости от внешних программ. Умение прослушивать сеть и посылать пакеты ping из Perl открывает целый диапазон возможностей. Сначала позаботимся о том, чтобы удалить простую зависимость.



Модуль Net: : Ping Рассела Мосмана (Russell Mosemann), который можно найти в дистрибутиве Perl, помогает проверить работоспособность сети. Net: : Ping позволяет посылать пакеты ping трех типов и проверять возвращаемые ответы: ICMP, TCP и UDP. ICMP-пакеты (Internet Control Message Protocol)- это «классика ping», и их посылает подавляющее большинство программ, производных от ping. У пакетов этого типа есть два недостатка:

1. Как и в случае с программой, вызывающей clog/fping, все сценарии Net: :Ping, использующие ICMP, необходимо выполнять с повышенными привилегиями.

2. Perl на MacOS в настоящее время не поддерживает ICMP. Возможно, в будущем это будет исправлено, а пока не следует забывать об этом ограничении переносимости.

Два других варианта пакетов Net: :Ping -это пакеты TCP и UDP. В обоих случаях пакеты посылаются на порт службы echo удаленной машины. Эти две возможности обеспечивают переносимость, но вам они могут показаться менее надежными, чем ICMP. ICMP встроен во все стандартные стеки TCP/IP, но не на всех машинах может быть запущена служба echo. В результате, если ICMP не отфильтровывается намеренно, вы получите ответы на ICMP-пакеты с большей вероятностью, чем на пакеты других типов.

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

ping: use Net::Ping;

$p = new Net::Ping("icmp"):

Этот объект очень просто использовать:

if ($c->pir,g("hcst")){

prmt "oirg succeeded '' " ': else{

pr:it "p:ng faiied\n": }

Теперь вернемся к сложной части нашего первоначального сценария, прослушиванию сети с помощью clog. К сожалению, с этого момента нам придется отпустить пользователей MacOS. Программа на Perl, которую мы собираемся рассматривать, привязана к библиотеке libpcap, о которой мы говорили раньше, поэтому применение программы где-либо, кроме Unix и его вариаций, затруднено или невозможно.

Первый шаг, который необходимо выполнить, - собрать библиотеку libpcap. Я советую скомпилировать и tcpdump. Как и в случае с утилитами из командной строки для UCD-SNMP, tcpdump можно использовать для выяснения возможностей libpcap перед тем, как писать на Perl, а также для перепроверки кода, написанного для этой программы.



Имея libpcap, легко скомпилировать модуль Net: :Pcap, первоначально написанный Питером Листером (Peter Lister), а затем полностью переписанный Тимом Поттером (Tim Potter). Этот модуль открывает вам доступ ко всем возможностям

libpcap. Давайте посмотрим, как можно использовать его для поиска пакетов SYN, как это делает clog.

Программа начинается с запроса о доступном/допускающем прослушивание сетевом интерфейсе и его настройках:

use Net::Pcap;

# поиск сетевого устройства, допускающего прослуиивание

$dev = Net: :Рсар: : icoKupdev(\$en ) ;

die "Невозможно найти подходящее устройство: $err\n" unless Sdev;

tf выясняем номер и маску сет;.> эгого \'Стоойстра die "Невозможно выяснить информацию об устройстве:

В большинстве функций libpcap действуют соглашения о кодах возврата, принятые в С, и возвращают 0 в случае успеха и -1 в случае неудачи, поэтому в программах, использующих Net: : Рсар, часто применяется идиома с:е :*' . . В страницах руководства по рсар(З) можно выяснить смысл аргументов, передаваемых каждой функции.

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

сохраненного ранее файла пакетов). Net :Рса;> 'km," _r.vo возвращает дескриптор, используемый для ссылки на этот сеанс:

 открываем интерфейс для "живого" захвата

 die "HeB03vof.no пол>чить дескригтоо $е:- -Г ariesb Sceseriar:

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

Что собой представляет пакет SYN? Чтобы понять это, нужно иметь представление о том, как собираются TCP-пакеты. Посмотрите на рисунок из RFC973, где приведен TCP-пакет и его заголовок.

Пакет SYN - это тот пакет, в заголовке которого установлен только флаг SYN . Для того чтобы lihpcap «знала», что надо перехватывать только такие пакеты, следует опреде лить, какой именно байт в пакете она должна искать. Каждый штрих н;1 верху соответствует одному биту, так что нетрудно подсчитать байты Тот же пакет.



Нам необходимо проверить, равен ли 13-й байт двоичному числу 00000010 (десятичное число 2). В качестве фильтра нам нужна строка. Если бы мы хотели найти пакеты, у которых установлен по крайней мере флаг SYN, то могли бы использовать строку tcp[13] & 2 ! = 0. Этот фильтр затем компилируется в программу фильтрации и устанавливается:

Sprog = "tcp[13]- = 2";

# компилируем и устанавливаем "программу фильтрации" die "Невозможно скомпилировать $prog\n"

if (Net::Pcap::compile($descript ,\$compprog,Sprog,0SneLmasK)) ; die "Невозможно установить фильтр\п"

if (Net::Pcap::setfilter($descript,Scompprog));

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

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

2. Ссылка на хэш, описывающая заголовок пакета (отметки о времени и пр.).

3. Копия всего пакета.

Начнем мы с очень маленькой процедуры, выводящей длину полученного пакета:

sub printpacketiength {

print length($_[2]),"\n": }

Имея нужную подпрограмму, мы ищем пакеты SYIJ:

die "Невозможно перехватить пакеты: ". Not : : Рсар: :ge:err(Sdebcr ipt) \:~"

if (Net, :Pcap: : loop($descr ip:. -i. &p- -.tpao^i-fjir. ' )).

die "Невозможно закрыта уст роиство\п" if (Net: :Pcap; :close($descnpt)):

Второй аргумент -1 метода Net: : Рсар: : 1оор( )определяет количество пакетов, которые мы хотим перехватить до выхода. В данном случае мы будем перехватывать пакеты до бесконечности.

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



Как и в примере из главы 5, работающем с DNS, нам придется разбить пакет на части. Обычно такая процедура требует обращения к спецификации (RFC) и создания нужных шаблонов unpack(). Тим Поттер (Tim Potter) проделал сложную работу и написал несколько модулей Net Packet: NetPacket::Ethernet, NetPacket::IP, NetPacket::TCP, NetPacket::ICMP и т. д. Каждый из них поддерживает два метода: strip() и decode().

Метод strip() просто возвращает данные из пакета, выкидывая все, что касается уровня сети. Запомните, что TCP/IP-пакет в сети Ethernet - это, на самом деле, обычный пакет TCP, «обернутый» в пакет IP, а тот, в свою очередь, обернут в пакет Ethernet. Так что если $pkt хранит TCP/IP-пакет, то NetPacket:: Ethernet: :strip($pkt) вернет IP-пакет (удалив уровень Ethernet). Если бы нам нужна была TCP-часть от $pkt, можно было бы использовать NetPacket: :IP: : st r ip( Net Packet: : Ethernet: : stnp($packet)) для удаления и 1Р-, и Ethernet-уровня.

decode() продвигается глубже еще на один шаг. Он разбивает пакет на его составляющие и возвращает экземпляр объекта, содержащего все эти части. Например:

NetPacket::TCP->decode(

NetPacket: :IP: : strip( Net Packet: ; Ethernet: :st>-ip($packet)))

вернет экземпляр объекта со следующими полями:




Поле



Описание

src_port

TCP-порт источника

dest_port

TCP-порт приемника

Seqnum

Порядковый номер

Acknum

Номер подтверждения

Hien

Длина заголовка

Reserved

6-битное «зарезервированное» пространство в TCP-заголовке

Flags

Флаги URG, АСК, PSH, RST, SYN и FIN


Winsize

Размер TCP-окна

Cksum

Контрольная сумма

Urg

Указатель на экстренные данные


Options

Любые TCP-параметры в двоичном виде


Data

Данные для пакета
Это уже должно быть знакомо читателю (рис. 10.2). Чтобы выяснить порт приемника для пакета, можно сделать следующее:

$pt = NetPacket::TCP->decode( NetPacket: :IP: :stnp(

NetPacket::Ethernet::strip($packet )))->{dest_port};

Теперь соберем все вместе и кое-что изменим. Поттер создал оболочку для инициализации и циклов Net: : Рсар и выпустил ее как модуль Net: : PcapUtils. Модуль обрабатывает некоторые из выполняемых нами шагов, делая наши программы короче. Продемонстрируем все это л действии, учитывая все, что мы узнали в последнем разделе:



use Net::PcapUtils;

use NetPaeket::Ethernet; use NetPacket::IP;

use Net::Ping;

# локальная сеть $localnet = "192.168.1";

# фильтр для поиска SYN- пакетов не из локальной сети $prog = "tcp[13] = 2 and src net not Slocalnet";

$| =1: tt снимаем буферизацию с STDIO

sub grab_ip_and_pmg{

my ($arg,$hdr,$pkt) = @_ ;

ft получаем IP-адрес источника $src in = lietPacket: : IP->(Jecoae(

print "$src_]p is ". (($p-'>ping($src_ip)} 9

"alive" : "unreachable"). "\,i" unless $cache{$src_ip}++; }

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

В воскресенье утром центральная группа поддержки из другого отдела нашла ошибку в настройках маршрутизатора. Студент в одном из общежитий установил на свою машину Linux и неверно настроил демон маршрутизации сети. Эта машина посылала широковещательные сообщения в университетскую сеть о том, что она является маршрутом по умолчанию. Неправильно настроенный маршрутизатор, обслуживающий наш отдел, услышав это сообщение, безропотно изменил таблицу маршрутизации и добавил второй маршрут во внешний мир. Пакеты будут приходить из внешнего мира, и этот маршрутизатор, честно выполняя свои обязанности, поровну разделит ответы между двумя маршрутами. Такое распределение, когда «один пакет посылается настоящему маршрутизатору, другой посылается на машину студента, один - настоящему маршрутизатору, другой - на машину студента», создало асимметричную ситуацию маршрутизации. Когда фиктивный маршрут был удален и были задействованы фильтры, предотвращающие появление такой ситуации в дальнейшем, наша жизнь вернулась к нормальному ритму. Я не буду говорить, что стало со студентом, вызвавшим эту проблему.

В этом разделе вы познакомились с применением модулей Net: :Р. Net: : PcapUtils и семейства модулей NetPacket: : * для диагностики. Не останавливайтесь на этом! Эти модули позволяют написать множество программ, способных помочь разобраться с проблемами в сети или активно наблюдать за сетью в поисках опасности.






Предотвращение подозрительных действий


Самый последний атрибут ночного сторожа, который нас интересует, это способность предотвращать. Это тот голос, который подсказывает: «Не стоит ставить на подоконник только что испеченный пирог, чтобы охладить его».

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

Цель, которую я преследую, состоит в предотвращении использования или хотя бы в уменьшении количества плохих паролей. Хорошие механизмы защиты становятся бесполезными из-за выбора плохих паролей. Паролем для возвращения в пещеру клана, скорее всего, было слово «ог». В наше время ситуация обостряется из-за повсеместного наплыва хитроумных программ для взлома паролей, таких как John the Ripper (Solar Designer, Солар Дизайнер), LOphtCrack (Mudge, Мадж и Weld Pond, Вельд Понд) и Crack (Alec Muffett, Алек Маффет)).

Единственный способ избежать уязвимости, которой подвержены ваши системы благодаря этим программам, - избавиться от плохих паролей. Вам нужно помочь пользователям получить пароли, которые сложно отгадать. Один из способов сделать это в Unix (хотя эту программу можно перенести и на NT, и на MacOS) - использовать libcrack, также написанную Алеком Маффетом. В процессе написания программы Crack Мяф-фет оказал огромную услугу системным администраторам, взяв несколько методов, используемых в Crack, и создав из них библиотеку проверки паролей, написанную на С.

В библиотеке имеется лишь одна функция для пользовательского интерфейса: FascistCheck(). Эта функция принимает двааргумента: строку для проверки и полный префикс пути для файла словаря, созданного при установке libcrack.

Функция возвращает либо NULL, если строка является «безопасным» паролем, либо объяснение, например, «это словарное слово» „ если пароль легко взломать. Было бы очень удобно, если бы существовала возможность использовать эту функциональность как часть программы на Perl, устанавливающей или меняющей пароли, так что давайте посмотрим, как можно написать модуль, применяя эту функцию. Нам потребуется заглянуть ненадолго в программу на С, но я обещаю, что это не займет много времени и пройдет безболезненно.




Первый наш шаг - собрать пакет libcrack с http://www.users.dlr-con.co.uk/~crypto/. Этот процесс подробно описан в дистрибутиве и не вызывает затруднений. Я лишь приведу пару советов:

Чем больше будет словарь, который вы соберете, тем лучше. Хороший источник для слов, которые можно включить в словарь, - это ftp://ftp.ox.ac.uk/pub/wordlists. Процесс сборки требует значительного дискового пространства (для процесса

sort в utils/mkdict), имейте это в виду.

Убедитесь, что вы собираете ilbcrack при помощи тех же средств разработки, что и Perl. Например, если при компиляции Perl вы пользовались

gcc, обязательно используйте gcc и при компиляции libcrack.

Это справедливо для всех модулей, которые нужно связать с дополнительными библиотеками С.

После того как библиотека С libcrack.a собрана, необходимо выбрать метод для вызова функции FascistCheck() из Perl. Для создания подобной связи существует два популярных метода: XS и SWIG. Мы будем применять XS, т. к. его легко использовать для простых задач, и все необходимые для этого инструменты входят в состав дистрибутива Perl. Подробное сравнение этих двух методов можно найти в книге «Advanced Perl Programming» (Углубленное программирование на Perl) Шрирама Щринивасана (Sriram Srinivasan) (O'Reilly).

Самый простой способ начать работать с XS - использовать программу h2xs для создания прототипа модуля:

$ h2xs -A -n Cracklib

Writing Cracklib/Cracklib.pm

Writing Cracklib/Cracklib.xs

Writing Cracklib/Makefile.PL

Writing Cracklib/test.pi

Writing Cracklib/Changes

Writing Cracklib/MANIFEST

Вот описание файлов, создаваемых этой командой (табл. 10.2). Таблица 10.2. Файлы, созданные командой h2xs -A n Cracklib

Чтобы получить нужную нам функциональность, следует изменить два файла. Начнем с более сложного: склейки с кодом на С. Вот как эта функция определяется в документации libcrack:




Имя файла



Описание

Cracklib/ Cracklib. рт


Заглушка с прототипами и документацией 

Cracklib/Cracklib.xs 

Склейка с кодом на С 

Cracklib/Makefile.PL 

Код на Perl для создания файла Makefile 

Cracklib /test.pl 

Тестовый код прототипа 

Cracklib/Changes 

Документирование версий 

Cracklib/MANIFEST 

Список файлов, входящих в состав модуля 
<


char pw

char 'dictpatn

Директива PROTOTYPES создает Perl- прототипы для функций из этого файла. В программе, которую мы пишем, это не имеет значения, но мы включаем директиву для подавления предупреждений в процессе сборки.

Сразу же после определения функции мы описываем, как она вызывается и что возвращает:

CODE:

RETVAL = (char «)FascistCheck(pw,dictpath);

OUTPUT:

RETVAL

RETVAL - это настоящая склейка. Она представляет собой точку передачи между кодом на С и интерпретатором Perl. Именно тут мы говорим Perl, что он должен получить строку символов, возвращенную библиотечной функцией FascistCheck(), и сделать их доступными в качестве возвращаемого значения (т.е. OUTPUT) Perl-функции Cracklib: : Fas-cistCheck(). Больше нам не придется иметь дело с кодом на С.

В другом файле, который нужно поменять, мы изменим только одну строку. Нам требуется добавить еще один аргумент в вызов WriteMake-f ile() в Makefile.PL, чтобы убедиться, что Perl может найти файл ПЬ-crack.a. Вот как выглядит эта новая строка в нашем контексте:

'LIBS' => [''], tt например, '-1т'

'MYEXTLIB' => '/usr/local/lib/libcrack$(LIB_EXT)' tt местоположение cracklio

'DEFINE' => '', например DHAVE_SOMETHING'

Это тот минимум, который необходим для работы модуля. Если мы наберем:

perl Makefile.PL

make

make install

то сможем начать использовать наш модуль примерно так:

use Cracklib:

use Term: : ReadKey: tt для чтения паролей

Sdictpath = "/usr/local/etc/cracklib/pw_dict";

prL.t "Введите пароль: ":

RearJMode 2; tt отключаем зьвод символов ";;

chomp($pw = Headline):?} читаем пароль

ReadMode 0: tt возвращаем *езмл з поедьллцее :ост:)«"ие

print "\n" $result = Oacklib: : FascistCheck($pw Sclictpath); i f (df.'f". ""<: ?' f'S'i1

Но не стоит использовать этот модуль в таком виде. Давайте, перед тем как устанавливать модуль, доведем его до профессионального уровня.

Во-первых, добавим сценарий, позволяющий удостовериться, что модуль работает корректно. Сценарий должен вызывать нашу функцию с некоторыми известными значениями и сообщать каким-нибудь специфичным образом, получил ли он правильные ответы. В самом начале проверки нужно напечатать диапазон номеров тестов. Например, если мы собираемся провести 10 тестов, нужно сначала напечатать 1. 10. Затем для каждого выполняемого теста следует напечатать либо «ok », либо «not ok» и номер теста. Стандартная программа сборки модуля интерпретирует этот вывод и выводит пользователю итоги результатов проверки.



h2xs

предоставляет пример сценария проверки, который можно изменять. Создадим каталог t (стандартный каталог, назначенный по умолчанию для проверки модуля) и переименуем test.pl в t/cracklib.t. Вот фрагмент кода на Perl, который нужно добавить в конец t /crack-lib.t для выполнения ряда тестов:

 местоположение файлов словарей Sclictpath = "/usr/local/etc/pw_dict";

 проверочные строки и известные для них ответы cracklib %test =

("happy" => "it is too short",

"a" => "it's WAY too short",

"asdtasdf" => "it does not contain enough DIFFERENT characters"

"minicomputer" => "it is based on a dictionary word"

"ftm2tgr3fts" => ""):

 Просматриваем в цикле все ключи из хэша. проверяя, возвращает

 ли cracklib предполагаемые ответа. Если да. то пишем "ок", в

противном случае -- "not ok"

$tcst.iur = 2;

fo^each $pw (кеуз %test){

my (Sresult) = Crackiib::FascistCnecK($pw.Sdictpatn); if ((defined $гез„1:

ana Sresul-f c-q S:est{$p.';)) or (!ae!iriea Sresb.l: ctj StesiiSpw} eq "");' pri''t "0'' ". $':estnui!!++, "

else i

Всего было сделано шесть тестов (пять из хэша %test и проверка загрузки модуля), значит, нужно изменить строку из t/cracklib.t с:

BEGIN {$|=1: print "1. . 1\: ) на:

BEGIN {$|=1: print "1..6\"; }

Теперь можно набрать make test и Makefile и запустить программу проверки, чтобы убедиться, что модуль работает верно.

Разумеется, сценарий проверки очень важен, но наш сценарий вряд ли заслужит уважение, если мы пропустим такой решающий компонент, как документацию. Потратьте время и дополните файлы Cracklib.pm и Changes, заменив заглушки на полезную информацию о модуле. Также неплохо добавить файл README или INSTALL, в котором рассказано, как собрать модуль, где найти нужные компоненты, такие как libcrack, приведены примеры программ и т. д. Об этих новых файлах и переименовании файла test.pl нужно сказать в файле MANIFEST, чтобы не вводить в заблуждение программу компиляции модуля.

Наконец, установите модуль там, где нужно. Используйте вызовы Cracklib: : FascistCheck() везде, где нужно установить или сменить пароли. Если количество плохих паролей в вашей системе уменьшится, «ночной сторож» с удовольствием одобрит вас.






Протокол SNMP


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

По правде говоря, простой

протокол управления сетью не очень-то и прост. С этим предметом связано много тонкостей. Если вы еще не знакомы с SNMP, загляните в приложение Е «Двадцатиминутное руководство по SNMP».

Использование протокола SNMP из Perl

Один из способов использовать протокол SNMP из Perl - вызвать программу, работающую в командной строке, наподобие UCD-SNMP, при веденной в демонстрационных целях в приложении Е. Этот процесс безопасность и наблюдение за сетью виден и ничем не отличается от вызова внешних программ, о чем мы раньше упоминали в книге. Ничему новому тут научиться нельзя, так что не будем уделять этому подходу много времени. Приведу лишь одно предостережение: тем, кто использует SNMPvl или SNMPv2C, скорее всего, придется указывать имя сообщества (community name) в командной строке. Если эта программа выполняется в многопользовательской системе, любой, кто обратится к списку процессов, сможет увидеть имя сообщества и завладеть «ключами от города». Эта угроза существует в

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

Если мы не вызываем внешнюю программу для выполнения SNMP-oneраций из Perl, другим вариантом является использование модуля SNMP. Существует по крайней мере три очень похожих модуля: Net: : SNMP Дэвида М. Тауна (David M. Town), SNMP_Session.pm, написанный Саймоном Лейненом (Simon Leinen) и «SNMP Extension Module v3.1.0 for the UCD SNMPvS Library» (Модуль SNMP расширений vS.l.O для библиотек UCD SNMPvS, который мы будем называть просто SNMP из-за способа его загрузки) Дж. С. Марзота (G.S. Marzot). Все три модуля реализуют SNMPvl. Net::SNMP и SNMP частично поддерживают SNMPv2. И лишь в SNMP предлагается некоторая поддержка SNMPvS.




Помимо различного уровня поддержки протокола SNMP, самое большое различие между этими модулями заключается в их зависимости от внешних библиотек. Первые два (Net: :SNMP и SNMP_Session.pm) реализованы только на Perl, a SNMP должен быть скомпонован с прекомпилированной библиотекой UCD-SNMP. Основной недостаток применения SNMP - это дополнительная зависимость и лишний шаг компиляции (если считать, что вы можете собрать библиотеку UCD-SNMP на своей платформе).

Положительная сторона зависимости от библиотеки UCD-SNMP в том, что она придает модулю дополнительную силу и мощь. Например, SNMP может анализировать файлы описания административных баз данных (Management Information Base, MIB) и выводить для анализа SNMP-na-

кеты, чего не могут два других модуля. Для уменьшения разницы в возможностях существуют другие модули (например модуль SNMP: :MIB: :Compiler Фабьена Тассэна (Fabien Tassin) способен анализировать MIB), но если нужно, чтобы один модуль выполнял всю работу, то лучше модуля SNMP ничего нет.

Давайте рассмотрим небольшой пример на Perl. Для того чтобы узнать количество интерфейсов на определенном устройстве, можно обратиться к переменной interfaces. ;fN'.jmDe<". Сделать это при помощи модуля Net: : SNMP очень просто:

use Net::SNMP;

 в качестве аргументов задаются имя >зяа и /msi еоибцес! в,;

(Ssession. $<;rro' ) = Net: :SNMP->session(,Hostnare = SARGVfOJ,

С(Ж<!Н,п ty = SARGVf" ] >.

die "Ошибка сеанса: Serror" unless ($snssion):

S iso. org. dod internet, mgnit. mib-2. interfaces.  ff-bmoer . 0 =

 1.36.1.2.1.2.1.0

Sresult = $session->get_request("l.3.6.1.2.1.2.1.0'):

die "Ошибка запроса: ". $session--->error unless (defined Sresult):

$session->close;

print "Количество интерфейсов: ".$result->{"1.3.6.1.2.1. 2. 1. 0"). "\n":

Если указать на рабочую станцию с интерфейсом обратной петли и интерфейсом Ethernet, программа вернет: Количество интерфейсов: 2; если же указать на портативный компьютер с интерфейсом обратной петли и интерфейсами Ethernet и РРР, то программа вернет Количество интерфейсов: 3; для небольшого маршрутизатора она вернет Количество интерфейсов: 7.



Важно обратить внимание на использование идентификаторов объекта (Object Identifiers, OID) вместо имен переменных. И Net::SNMP, и SNMP_Session.pm обрабатывают взаимодействие только по протоколу SNMP. Они не претендуют на выполнение каких-либо второстепенных задач, связанных с SNMP, например, анализ описаний SNMP MIB. Для выполнения этих действий нужно обратиться к другим модулям, таким как SNMP: :MIB: : Compiler или SNM.P_uЈt/.pm Майка Митчела (Mike Mitchell) для применения их с SNMP_Sess ion. pm (не путайте с модулем SNMP: : Utn

'. Вейна Маркетта (Wayne Marquette), который используется с модулем SNMP).

Для тех, кто предпочитает работать с текстовыми идентификаторами вместо численных, не создавая самостоятельно таблицу соответствия и не используя дополнительные модули, остается единственный вариант - обратиться к модулю SNMP, в котором есть встроенный анализатор MIB. Давайте рассмотрим таблицу ARP (Address Resolution Proto col, протокол преобразования адресов) на машине при помощи этого модуля:

use SNMP;

 в качестве ап'умен'ов задаются имя

Ssessio0 = new SNMP: : SessionfDestHosr -> $ARGVTO].

UseSprintValue => 1) die "Ошибка создания сессии: $SNMP: : Session. fr'orStr" unless

(defined Ssession)

n создаем структуру данных дли команды getnex4

$vars = new SNMP: :VarList([' iDNettoMediaNof Acc'-f-ss ]

['ipNetToMediaPhysAdd'eoF'}):

 получаем первую запись

($ip,$mac) = $session->getnext($vars):

die $session->{ErrorStr} if ($session->(ErrorStr});

# и все последующие

while (!$session->{ErrorStr} and

$$vars[0]-->tag eq "ipNetToMediaNetAddress"){

print "$ip -> $mac\n":

($ip,$mac) = $session->getnext($vars):

};

Вот как выглядит пример вывода этой программы:

192.168.1.70 -> 8:0:20:21:40:51

192.168.1.74 -> 8:0:20:76:7с:85

192.168.1.98 -> 0:сО:95:еО:5с:1с

Этот пример похож на предыдущий, где использовался модуль Net: : SNMP. Для выявления различий рассмотрим его подробнее:

use SNMP;



Ssession = new SNMP::Session(DestHost => $ARGV[0], Community => $ARGV[1],

UseSpnntValue => 1);

После загрузки модуля SNMP мы создаем объект сессии так же, как и в случае с Net: :SNMP. Дополнительный аргумент UseSprintValue => 1 указывается лишь для того, чтобы выводить возвращаемые значения более аккуратно. Если этого не сделать, то Ethernet-адреса будут выводиться в закодированном виде.

# создаем структуру данных для команды getnext

$vars = new SNMP::VarList(['ipNetToMediaNetAddress'].

['ipNetToMediaPhysAddress']);

SNMP со своими командами использует такие простые строки, как sys-Descr. О, но предпочитает работать со специальным объектом, который называет «Varbind». Модуль применяет эти объекты для хранения значений, возвращаемых в результате запросов. Например, в нашей программе для отправки запроса get-next-request вызывается метод getnext(), прямо как в примере таблицы маршрутизации из приложения Е. Правда, на этот раз SNMP сохранит полученные индексы в Varbind, и нам не придется вручную следить за ними. Используя этот модуль, достаточно передать Varbind методу getnext, если необходимо получить следующее значение.

Varbind - это обычный анонимный Perl-массив, состоящий из четырех элементов: oir , i id, vai и type. Нас интересуют только. Первый элемент, obj - это объект, к которому посылается запрос. он может

быть задан в одном из нескольких форматов. В данном случае мы пользуемся форматом leaf identifier, т. е. определяем лист дерева, с которым мы связаны. IpNetToMediaNetAddress - это лист дерева:

iso.org. dod. internet, mgmt. mb-

 ip. ipMetToMediaTable. ipNetToMediatntry. ioNetTcMe3ia:jetAcd'ess

Второй элемент в Varbind- это iid, или идентификатор экземпляра (instance identifier). В предыдущем примере мы использовали только (например system. sysDescr. 0), поскольку видели объекты, имеющие

только один экземпляр. Скоро мы увидим примеры, где iid может иметь и другие значения, отличные от нуля. Например, позже мы сошлемся на определенный сетевой интерфейс в коммутаторе с несколькими Ethernet-интерфейсами. Для get необходимо указывать только два компонента Varbind- obj и iid. Методу getnext iid не нужен, т. к. он по умолчанию возвращает следующий экземпляр.



В приведенной выше строке используется метод VarList(), создающий список из двух Varbind, для каждого из которых определен только один элемент obj. Этот список мы предаем методу getnext():

# получаем первую запись

($ip,$mac) = $session->getnext($vars);

die $session->{ErrorStr} if ($session->{ErrorStr});

getnext() возвращает значения, полученные из запроса, и соответствующим образом обновляет структуры данных Varbind. Теперь остается только вызывать get next () до тех пор, пока мы не дойдем до конца таблицы:

while (!$session->{ErrorStr} and

$$vars[0]->tag eq "ipNetToMediaNetAddress"){

print "Sip -> $mac\n";

($ip,$mac) = $session->getnext($vars);

};

Давайте вернемся к миру безопасности, чтобы рассмотреть последний пример о SNMP. Задачу, которую мы будем решать, сложно или, по крайней мере, скучно выполнять при помощи имеющихся утилит командной строки.

Задача заключается в следующем: вас попросили выследить в комму тируемой сети Ethernet (switched Ethernet network) плохо ведущего себя пользователя. Единственная информация, которой вы обладаете, это Ethernet-адрес машины, на которой работает пользователь. Это но Ethernet-адрес, который содержится в файле (сам файл можно хранить в базе данных узлов, рассмотренной в главе 5, если эту базу несколько расширить), а прослушать коммутируемую сеть у вас не получится, так что придется проявить сообразительность, чтобы вычислить эту машину.

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

Для

большей конкретизации скажем, что сеть состоит из нескольких коммутаторов Cisco Catalyst 5500; это даст нам возможность указать на конкретные переменные MIB. Основные методы, которые мы будем использовать для решения этой проблемы, также применимы для других продуктов и других производителей. Если информация относится только к определенному коммутатору или производителю, мы об этом скажем. А теперь давайте шаг за шагом рассмотрим решение проблемы.



Как и раньше, сначала необходимо выполнить поиск по корректным файлам модулей MIB. Обратившись к службе технической поддержки Cisco, мы узнаем, что нам понадобится доступ к четырем объектам:

vlanTable, которую можно найти в enterprises.Cisco.workgroup.cis-coStackMIB. vlanGrp из описания CISCO-STACK-MIB.

dotldTpFdbTablc (таблица прозрачной трансляции портов), которую можно найти в dotldBridge. dotldTp из описания RFC1493 BRIDGE-MIB.

dotldBasePortTable, которую можно найти в dotldBridge. rlotldBase в том же RFC.

ifXTable, которую можно найти в RFC1573 IF-MIB (Интерфейсы).

Зачем нужны четыре различные таблицы? В каждой из них есть что-то нужное нам, но ни в одной нет целиком всей информации, которую мы ищем. Первая таблица предоставляет список VLAN (Virtual Local Area Networks, виртуальные локальные сети), или виртуальных «сегментов сети» на коммутаторе. В Cisco решили хранить на коммутаторе отдельные таблицы для каждой виртуальной локальной сети, поэтому нам придется за один раз запрашивать информацию об одной виртуальной локальной сети. Подробнее об этом чуть позже.

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

Таблицы, связывающей номер порта (bridge port) с именем физического порта, не существует (что было бы очень просто), но dot loBasePo позволяет выяснить соответствие между номером порта и номером нн- Протокол SNMP интерфейса. Имея номер интерфейса, можно найти его в таблице ifXTable

и получить имя порта.

Вот схема четырехуровневого согласования, необходимого для выполнения поставленной перед нами задачи (рис. 10.1).

А вот программа, в которой все четыре таблицы собраны вместе для вывода нужной информации:



use SNMP;

tt Дополнительные модули MIB, нужные нам, которые можно найти з

# том же каталоге, что и сценарий

$ENV{'MIBFILES' } =

"CISCO-SMI.my:FDDI-SMT73-MIB.my:CISCO-STACK-MIB.my:BRIDGE-MIB.my ' :

tf соединяемся и получаем список виртуальных локальных сетей с

tf этого коммутатора

Ssession = new SNMP::Session(DestHost => $ARGV[0],

Community => $ARGV[1]);

die "Ошибка создания сессии: $SNMP::Session::ErrorStr" unless

(defined Ssession);

if enterprises. cisco.workgroup.ciscoStackMIB. vlanGrp. vlanTable. vlanEnrry

tt из CISCO-STACK-MIB

Svars = new SNMP::VarList(['vlanlndex' ]);

Svlan = $session->getnext($vars);

die $session->{ErrorStr) if ($session->{ErrorStr});

while (!$sess]on->{ErrorStr} and $$vars[0]->tag eq "vlanlndex"){

tf Ha CISCO CATALYST 5XXX просто не может быть более 1000

tt виртуальных локальных сетей (этот предел, скорее всего,

tf отличается для различных коммутаторов)

push(@vlans,$vlan) if $vlan < 1000;

Svlan = $session->getnext($vars):

};

undef Ssesaion,$vars;

на Cisco 5000

UseSprintVaiue => 1);

die ириска создания сессии. SSNMP . : ьеьъ^и! ..сr ruiЫ

I,: less (defined Ssession)

 dotldBi idgo.aotldTp.dotldTpFdoTable.do-.rdTpFaprr/ -,

# .13 RFC1493 BRIDGE-MIB

$vars = new S'iMP: :VarLisi(['dotiaTprcoAcaress1 ]. [ coticToFaoPort' ]):

(Smacaddr, Sportnum) = $sessior,->getnex* ($vars):

die $session-> {ErrorStr} if" ($session->{Erroi'Str/):

while ('$session->{ErrprStr} and

$$vars[0]-nag eq "dot1dTpFdpAddress"){

n ddtldBridge.dotIdBase.dotIdBasePortTable.dotIdBasePortEn try

и из RFC1493 BRIDGE-MIB

$ifnum =

(exists $ifnum{$portnum/) ? $ifnum{$portnuir\} :

($ifnum{$portnum} =

Ssession->get("dotIdBasePortIfIndex\.Sportnum")):

# из ifMIB.ifMIBODjects.ifXTable.ifXEntry из RFC1573 IF-MIB

Sportname =

(exists $portname{$ifnum}) ? $portnarne{$ifnum} :

($portname{$ifnum}=$session->get("ifName\.Sifnum"));

print "Smacaddr в виртуальной локальной сети $vlan на $portname\n":



(Smacaddr,Sportnum) = $session->getnext($vars):

}:

undef Ssession, $vars, %ifnum, ^oportname1;

}

Если вы уже читали приложение Е, то большая часть программы должна быть вам знакома. Приведем лишь комментарии по новым фрагментам:

$ЕМУ{ 'MIBFILES'h

"CISCO-SMI.пу:FODI-SMT73-MIB.my;CISCO-STACK-MIB.my:BRIDGE-MIB.my":

Эта программа устанавливает переменную окружения MIBFILES для библиотеки UCD-SNMP. Будучи установленной, эта переменная дает инструкцию библиотеке проанализировать приведенный список дополнительных файлов для определения объекта MIB. В этом списке присутствует один странный файл модуля MIB - FDDI SMT73MIB.my. Он добавлен из-за того, что CISCO-STACK-MIB.my имеет следующий оператор для включения некоторых определений из других записей MIB:

IMPORTS

MODULE-IDEiiTITr. OBJECT-TYPE. Integor32, IpAaor-sb,

FROM SNMPv2-SMI

DisplayString, RowStatus

FROM SNMPv2-TC

fddimibPORTSMTIndex, fddimibPORTIndex

FROM FDDI-SMT73-MIB

OwnerString

FROM IF-MIB

MODULE-COMPLIANCE, OBJECT-GROUP

FROM SNMPv2-CONF

workgroup

FROM CISCO-SMI;

Хотя мы и не ссылаемся на объекты, использующие fddimibPORTSMTI

dex или fddimibPORTIndex, мы все же добавляем (намеренно) этот файл в список, чтобы анализатор MIB не выдавал сообщений. Все остальные определения MIB из этого оператора IMPORTS включаются либо в списке, либо в списке по умолчанию библиотеки. При работе с MIB вам часто придется заглядывать в раздел IMPORTS модуля MIB для изучения зависимостей этого модуля.

Двигаясь дальше, мы находим еще один странный оператор:

$sess]on = new SNMP::Session(DestHost => $ARGV[0]

Community => $ARGV[ 1]. >". Svlan.

UseSprintValue => 1):

Вместо того чтобы просто передать имя сообщества, введенное пользователем, мы дописываем к нему нечто вроде .WL AN-NUMBER. На жаргоне Cisco это означает «индексация строки сообщества». При работе с виртуальными сетями и мостами устройства Cisco следят за несколькими «экземплярами» MIB, по одному на каждую виртуальную сеть. Наша программа выполняет одни и те же запросы для каждой виртуальной сети, найденной на коммутаторе:



Sif.n'jrr =

Приведем два комментария к этому отрывку. Во-первых, по ряду причин мы используем в качестве аргумента get:() простую строку. Хотя с таким же успехом это могло быть что-то более Varbind-подобное:

Во-вторых, обратите внимание, что тут мы выполняем простое кеширование. Перед тем как выполнять де, мы смотрим в простую хэп:-таблицу (%1*пич), чтобы проверить, выполнялся ли уже этот запрос. Ее ли нет, то запрос выполняется, а его результаты помещаются таблицу. После просмотра всей виртуальной локальной сети кэширующий хэш удаляется (uruef %i'Tj:ii)1, чтобы исключить возможность

дезинформации при использовании данных для другой виртуальной

локальной сети.

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

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

"00 10 1F 2D F8 FB " в виртуальной локальной сети 1 на 1/1

"00 10 1F 2D F8 FD " в виртуальной локальной сети 1 на 1/1

"08 00 36 8В А9 03 " в виртуальной локальной сети 115 на 2/18

"08 00 36 ВА 16 03 " в виртуальной локальной сети 115 на 2/3

"08 00 36 D1 СВ 03 " в виртуальной локальной сети 115 на 2/15

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