Протокол 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, мы узнаем, что нам понадобится доступ к четырем объектам:
Зачем нужны четыре различные таблицы? В каждой из них есть что-то нужное нам, но ни в одной нет целиком всей информации, которую мы ищем. Первая таблица предоставляет список 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
Эту программу улучшить нетрудно. Помимо более аккуратного и более упорядоченного вывода можно сохранять состояния между запусками. При каждом запуске программа могла бы сообщать об изменениях: какие адреса появились, какие порты изменились и т. д. Правда, нужно заметить, что большая часть коммутаторов относится к разновидности «обучаемых», поэтому они считают устаревшими записи об адресах, которые они давно не встречали. Это означает, то программу необходимо запускать как минимум с той же периодичностью, с которой устаревает информация о порте.