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

         

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



Таблица Е.1. Поддеревья узла internet( 1)



Поддерево Описание
Directory(1 ) Каталог OSI
mgmt(2) Стандартные объекты RFC
Experimental^) Эксперименты с Internet
Private(4) Зависит от производителя
Security(S) Безопасность
snmpV2(6) Описание работы SNMP

Поскольку нас интересует применение SNMP для управления устройствами, мы обратимся к ветви mgmt(2). Первый узел под mgmt(2) - сам MIB (это почти рекурсия). А раз существует только один MIB, то единственный узел под mgmt(2) - это mib-2(1).

По-настоящему MIB начинается с этого уровня в дереве. Мы ищем первую группу ветвей, называемых группами объектов, где хранятся переменные, к которым хотим обратиться:

system(1)

interfaces(2)

at(3)

ip(4)

icmp(5)

tcp(6)

udp(7)

egp(8)

cmot(9)

transmission(10)

snmp(11)

Раз мы ищем «системное описание» SNMP-переменной, будет логично посмотреть в группу system( 1). Первый узел в этом дереве - sysDescг (1). Мы нашли то, что искали.

Зачем связываться с обходом деревьев? В результате такого путешествия можно получить идентификатор объекта sysDescr(1). Идентификатор объекта, или OID, - это всего лишь набор чисел, разделенных точками, с каждого уровня дерева, которые встречались на пути к объекту. Вот как это выглядит в графическом виде.

Поскольку OID для дерева Интернета - это 1. 3. 6.1, то OID для системной группы объектов- 1.3.6.1.2.1.1, a OID для объекта sysDescr-1.3.6.1.2.1.1.1.

Если нужно воспользоваться таким идентификатором на практике, то для получения значения этой переменной следует добавить к идентификатору еще одно число. Нам нужно добавить 0, что соответствует
первому (и единственному, т. к. устройство не может иметь более одного описания) экземпляру этого объекта.

Что ж, давайте так и поступим; воспользуемся этим идентификатором, рассматривая SNMP в действии. В этом приложении будут применяться в демонстрационных целях инструменты из пакета UCD-SNMP. Па-
кет UCD-SNMP, который можно найти на http://ucd-snmp.ucdavis.edu/, является отличной бесплатной реализацией SNMPvl и SNMPvS. Мы используем именно эту реализацию SNMP, т. к. один из модулей Perl
связан с его библиотекой, но и все остальные клиенты, которые могут посылать SNMP-запросы, действуют практически так же. После знакомства с SNMP-утилитами командной строки читателю будет легко переходить на эквиваленты в Perl.

Инструменты командной строки из UCD-SNMP требуют ставить первой точку в ОШ/имени переменной, если оно начинается от вершины дерева. В противном случае считается, что OID/имя переменной начинается с вершины дерева mib-2. Вот два способа получить системное описание для машины solarisbox:

$ snmpget solarisbox public .1.3.6.1.2.1.1.1.0 $ snmpget solarisbox public .iso.6rg.dod.internet.mgmt.mib-2.sys- tem. sysDescr. 0

Обе эти строки позволяют получить одно и то же:

system.sysDescr.О = Sun SNMP Agent, Ultra-1

Но вернемся к теории. Очень важно помнить, что «Р» в SNMP означает протокол (Protocol). Сам SNMP - это всего лишь протокол для взаимодействия между элементами в управляющей инфраструктуре.
Операции, или единицы данных протокола (Protocol Data Units, PDU), считаются простыми (simple). Вот какие единицы PDU встречаются чаще всего, особенно при программировании на Perl:

get-request

get-request- «рабочая лошадка» в семействе PDU. Посредством get-request опрашивают SNMP-объект, для того чтобы получить значение SNMP-переменной. Очень многие при работе с SNMP никогда не используют ничего, кроме этой операции.

get-next-request

get-next-request очень похож на get-request, только он возвращает элемент из MIB, который находится после указанного («первый лексикографический наследник» (first lexicographic successor) в
терминах RFC). Эта операция бывает полезна, если необходимо найти все элементы из логической таблицы. Например, можно послать несколько последовательных запросов get-next-requests для
обращения к каждой строке из ARP-таблицы рабочей станции.
Очень скоро будет приведен пример этой операции в действии.

set-request

set-request делает именно то, что можно ожидать; он пытается изменить значение SNMP-переменной. Эта операция служит для изменения конфигурации SNMP-совместимых устройств.

trap/snmpV2-trap

trap - это имя SNMPvl, a snmpV2-trap - имя из SNMPv2/3. Обсуждение прерываний лежит за рамками данной книги, но, коротко говоря, они позволяют получить информацию о событиях (перезагрузка или достижение границы счетчиком) с SNMP-совместимой машины без явного запроса подобной информации.

response

response - это операция, применяемая для передачи ответа любой из операций. Ее можно использовать для ответа на get-request, для сообщения о том, успешно ли завершилась операция set-request и т. д.
Вам редко придется явно применять эту операцию при программировании, т. к. большая часть SNMP-библиотек, программ и Perl-модулей обрабатывают ответы автоматически. Тем не менее, очень
важно понимать не только правила построения запросов, но и особенности генерирования ответов.

Если читатель никогда раньше не имел дела с SNMP, естественная реакция на приведенный выше список была бы такой: «Что это? Получить, установить, сказать о том, что что-то произошло, и это все?» Но
простой по замыслу создателей SNMP отнюдь не противоположность мощному. Если производитель SNMP-устройства правильно выберет переменные, при помощи протокола можно будет сделать практически все. Классический пример из RFC - это перезагрузка SNMP-совместимого устройства. Специальных операций для «запроса на перезагрузку» может и не существовать, но производитель сумеет легко реализовать эту операцию, используя триггерную переменную SNMP для хранения количества секунд, прошедших до загрузки. Когда эта переменная изменяется через set-request, перезагрузку устройства можно выполнить в указанное время.

Но если доступны такие возможности, то что предпринимается в плане безопасности, чтобы любой, у кого есть SNMP-клиент, не смог бы перегрузить вашу машину? В более ранних версиях протокола меха-
низм защиты был очень слаб. На самом деле, некоторые даже расшифровывали аббревиатуру SNMP как «Security Not My Problem» (Безопасность — не моя проблема) из-за слабого механизма аутентификации
в SNMPvl. Для того чтобы разобраться в тонкостях механизма защиты, т. е. понять, кто, что и как, нужно разобраться с терминологией, так что потерпите.

SNMPvl и SNMPv2C позволяют определить административные отношения между SNMP-объектами, известными как сообщества (соттиnltles). Сообщества - это способ группировки SNMP-агентов со сходными ограничениями на доступ с управляющими объектами, которые удовлетворяют этим ограничениям. Все объекты из сообщества имеют одно и то же имя сообщества. Для доказательства того, что вы являетесь членом сообщества, необходимо знать только имя сообщества.
Это и есть составляющая «кто имеет доступ?»

Что касается составляющей «к чему у них есть доступ?», в RFC1157 части MIB, имеющие отношение к определенному элементу сети, называются SNMP MIB view. Например, SNMP-совместимый тостер не
будет поддерживать те же конфигурационные переменные SNMP, что и SNMP-совместимый маршрутизатор.

Каждый объект в М1В определен как read-on Ly (доступный только для чтения), read-write (доступный для чтения/записи) или попе (нет доступа). Это называется режимом SNMP-доступа (SNMP access mode)
для объекта. Если объединить SNMP MIB view и режим SNMP-доступа, будет получен профиль сообщества SNMP (SNMP community pro file), описывающий тип доступа, предоставленный переменным в MIB определенным сообществом.

Теперь, объединив части кто и что, мы получим политику доступа SNMP (SNMP access policy), описывающую, какие типы доступа предлагают друг другу члены определенного сообщества.

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

! устанавливаем имя доступного только для чтения сообщества в

MyPublicCommunityName

snmp-server community MyPublicCommunityName RO

! устанавливаем имя доступного для чтения и записи сообщества в

MyPrivateCommunityName

snmp-server community MyPrivateCommunityName RW

В Solaris можно было бы добавить эти строчки в файл /etc/snmp/conf/snmpd.conf:

read-community MyPublicCommunityName write-community MyPrivateCommunityName

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

В более поздних версиях SNMP, в частности в версии 3, безопасность протокола была значительно улучшена. В RFC2274 и RFC2275 определяется пользовательская модель безопасности (User Security Model, USM) и модель управления доступом «View-Based» (View-Based Access Control Model, VACM). USM предоставляет криптографическую защиту аутентификации и шифрует сообщения. VACM предлагает исчерпывающий механизм управления доступом для объектов MIB. Эти механизмы пока еще являются относительно новыми и нереализованными (например, только один из Perl-модулей поддерживает их, но и такая поддержка появилась совсем недавно). Мы не будем обсуждать здесь эти механизмы, но вам, вероятно, стоит внимательно изучить RFC, т. к. популярность SNMPvS увеличивается.

SNMP на практике

Теперь, когда вы получили изрядную дозу теории SNMP, применим эти знания на практике. Вы уже знаете, как запросить системное описание для машины (помните краткое введение, приведенное ранее).
Рассмотрим еще два примера: запрос времени непрерывной работы системы и таблицы маршрутизации IP. До сих пор вы полагались только на мои слова при поиске местоположения и имени SNMP-переменной в MIB. Это нужно изменить, поэтому первый шаг при запросе информации через SNMP - это процесс,
который я называю «MIB groveling» («раболепство перед MIB»):

Шаг1

Найти нужный документ MIB. Если вы ищете независимые от устройства настройки, которые можно встретить на любом SNMP-устройстве, скорее всего, они найдутся в RFC1213. Если вам нужны имена переменных, зависящих от производителя, например, переменной, в которой хранится «цвет мерцающей лампочки на передней панели конкретного ATM-коммутатора», то следует обратиться к производителю коммутатора и запросить копию их модуля MIB (MIB module). Я педантично отношусь к применяемым терминам, потому что нередко можно услышать, как люди неверно говорят: «Мне нужна база MIB для этого устройства». В мире существует только одна база MIB; все остальное находится где-то в ее структуре (обычно где-то под ветвью private(4)).

Шаг 2

Найти в описаниях MIB нужную вам SNMP-переменную. Чтобы упростить второй шаг, разберемся с форматом.

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

Информация MIB записывается в соответствии со стандартным соглашением OSI (Open Systems Interconnection, взаимодействие открытых систем) подмножества ASN.l (Abstract Syntax Notation One, язык для описания абстрактного синтаксиса данных). Описание этого подмножества и другие сведения об описании данных для SNMP можно найти в RFC под названием «Structure for Management Information» (SMI).
Они дополняют RFC, определяющие протокол SNMP и текущую базу МШ. Например, самое последнее (на момент готовности рукописи этой книги) описание протокола SNMP можно найти в RFC1905, описание
самой последней базы MIB, с которой работает протокол, - в RFC1907, a SMI для этой базы MIB - в RFC2578. Я обращаю на это ваше внимание, поскольку нередко приходится обращаться к разным документам при поиске определенного SNMP-объекта.

Воспользуемся этими знаниями, чтобы решить первую задачу: выясним, используя SNMP, время непрерывной работы машины. Вопрос довольно общий, так что вероятность найти нужную SNMP-переменную в RFC1213 очень велика. Быстрый поиск «uptime» в RFC1213 позволяет получить такой отрывок из ASN.1:

sysUpTime OBJECT-TYPE

SYNTAX TirueTicks

ACCESS read-only

STATUS mandatory

DESCRIPTION

"The time (in hundredths of a second) since the network management portion of the system was last re-initialized."

::= { system 3 }

Внимательно разберемся в этом определении:

sysUpTime OBJECT-TYPE

Эта строка определяет объект под названием sysUpTime.

SYNTAX TimeTicks

Это объект типа TimeTicks. Типы объектов определяются в SMI, о них упоминалось совсем недавно.

ACCESS read-only

Этот объект доступен только для чтения через SNMP (т. е. get-req-uest); его нельзя изменить (т. е. set-request).

STATUS mandatory

Данный объект должен быть реализован в любом SNMP-агенте.

DESCRIPTION...

Это текстовое описание объекта. Всегда внимательно читайте все, что написано в подобном поле. В этом определении нас ждет сюрприз. sysUpTime показывает только то время, которое прошло с момента последней инициализации части системы, имеющей отношение к управлению сетью («the network management portion of the system was last re-initialized»). Это означает, что мы можем узнать только время непрерывной работы с момента последнего запуска SNMP-агента. Почти всегда это время совпадает с временем непрерывной работы самой системы, так что если вы заметите отклонение, то причина, скорее всего, будет в этом.

: := { system 3 }

Эта строка определяет, где именно в дереве MIB хранится объект. Объект sysUpTime - это третья ветвь дерева системной группы объектов. Подобная информация также позволяет получить часть идентификатора объекта, которая понадобится позже.

При желании запросить эту переменную с машины solarisbox в сообществе, доступном только для чтения, можно было бы использовать такую командную строку из UCD-SNMP:

$ snmpget solarisbox MyPublicCommunityName system.sysUpTime.0

В результате получим:

system.sysUpTime.О = Timeticks: (5126167) 14:14:21.67

To есть агент был последний раз инициализирован 14 часов назад.

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

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

Пришло время привести второй, более совершенный SNMP-пример:
сброс содержимого таблицы маршрутизации устройства. Сложность этого примера состоит в том, что надо обрабатывать группу скалярных данных как одну логическую таблицу. Для получения этих данных вы-
полним операцию get-next-request. Наш первый шаг на пути к цели - найти описание MIB таблицы маршрутизации. Поиск по «route» в RFC1213, в конечном счете, выдает следующее определение:

-- The IP rowing table contains an for each route

-- prese"tiy k'lOvvn to this entity

-- известного в настоящий момент этому элементу )
ipRouteTable OBJECT-TYPE

SYNTAX SEOUhNCt OF IpHouteEntry

ACCESS not-accessible

STATUS mandatory

DESCRIPTION

"This entity's IP R&.jting table."

Это определение не слишком отличается от того, которое мы видели минутой раньше. Отличия есть только в строках ACCESS и SYNTAX. Строка ACCESS предупреждает, что этот объект является всего лишь структурной единицей, представляющей целую таблицу, а не настоящей переменной, к которой можно направить запрос. Строка SYNTAX говорит о том, что таблица состоит из целого ряда объектов IpRouteEntгу. Посмотрим на начало определения IpRouteEntry:

ipRouteEntry OBJECT-TYPE
SYNTAX IpRouteEntry
ACCESS not-accessible
STATUS mandatory
DESCRIPTION

"A route to a particular destination."
INDEX { ipRouteDest }
::= { ipRouteTable 1 }

Строка ACCESS говорит, что мы нашли еще один «заполнитель» (placeholder), на этот раз для каждой строки из таблицы. Но этот заполнитель может нам кое-что поведать. Можно обратиться к каждой строке,
используя индексацию, т. е. объект ipRouteDest для каждой строки.

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

Определение ipRouteEntry выглядит так:

ipRouteEntry ::=
SEQUENCE {

ipRojteDest

IpAddress
ipRoutelfIndex

INTEGER.
ipRouteMetrid

INTEGER.
ipRouteMetric2

INTEGER.
ipRouteHetric3

INTEGER
ipRouteMetric4

INTEGER,
]pRouluNextHop

IpAddress.
ipRoufnTypp

INTEGER
ipRouteProto

INTEGER,
ipRouteAge

INTEGER,
ipRouteMask

IpAddress,
ipRouteMetricS

INTEGER,
ipRoutelnfo

OBJECT IDENTIFIER
}

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

ipRouteOesf OBJECT-TYPE

SYNTAX IpAddress

ACCESS "Gad-write

STATUS mandatory

DESCRIPTION

"Tne destination IP address of this route. An
entry with a value of 0.0.0.0 Is considered a
default route. Multiple routes to a single
destination can appear in the rabid, bu1; access to
such multiple eniries is depende:ч о:'; the tabJe-
access mechanisms defined by the network
management protocol in use."
: :-- ! ipRouteEntry 1

}

ipRoutelflndex OBJECT-TYPE

SYNTAX INTEGER

ACCESS read-write

STATUS mandatory

DESCRIPTION

"The index value vvhich jnicueiy identifies tke
local i.VLbrface th^ougn ,-;ricn the next r'cp jf t:ks
interface as identified b« tre sa~:e value of
]fli;oex."

Графическое представление ipRouteTable поможет вам разобраться с этой информацией.

Если вы разобрались с этой частью MIB, то на следующем шаге следует запросить информацию. Данный процесс называется «обходом таблицы». В большинство SNMP-пакетов входит утилита командной строки, называемая либо snmptable, либо snmp-tbl, выполняющая такой обход, но утилиты могут и не предоставить нужной вам степени контроля. Например, возможно, нужна не вся таблица маршрутизации, а
лишь список ipRouteNextHops. Кроме того, в некоторых SNMP-пакетах Perl просто нет подпрограмм для обхода деревьев. Так что имеет смысл знать, как выполнять все эти действия вручную.

Чтобы было проще понять, как это делается, я приведу информацию, которую мы в конечном итоге собираемся получать от устройства. Это позволит вам увидеть, как каждый шаг добавляет в таблицу еще одну строку собираемых данных. Если зарегистрироваться на удаленной машине (вместо того чтобы использовать SNMP для удаленного запроса) и набрать netstat —nr для сброса таблицы маршрутизации, вывод может выглядеть так:

default 192.168.1.1 UoS 0 215345 tu0

127.0.0.1 127.0,0.1 UH 8 5404381 lo0

192.168.1/24 192.168.1.189 U 15 9222638 tu0

Это соответствует внутреннему интерфейсу обратной петли по умолчанию и локальным сетевым маршрутам.

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

$ snmpgetnext computer public ip.IpRouteTable.ipRouteEntry.ipRouteDest
Ip.ipRouteTable.ipRouteEntry.ipflouteNextHop

ip.ipRouteTable.ipRouteEntry.ipRouteDest.0.0.0.0 = IpAddress: 0.0.0.0
ip.ipRouteTable.ipRouteEntry.ipRouteNextHop.0.0.0,0 = IpAddress: 192.168.1.1

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

Получив первую строку, можно выполнить еще один вызов get-next-request, на этот раз, используя индекс. Запрос get-next-request всегда возвращает следующий элемент из MIB, так что достаточно передать
ему индекс строки, которую мы только что получили, для перехода к следующей после нее строке:

$ snmpgetnext gold public ip.ipRouteTable. ipRouteEntry. ipRouteDest. 0.0.0.0
ip.ipRouteTable.ipRouteEntry.ipRouteNextHop. 0. 0. 0. 0

ip.ipRouteTable.ipRouteEntry.ipRou+eDest.127. 0.0. 1 = IpAddress: 127.0.0.1
ip.ipRouteTable.ipRouteEntry.ipRouteNextHop.127.0. 0.1 = IpAddress: 127.0.0.1

Вероятно, читатель уже догадался, каким будет следующий шаг. Мы используем еще один запрос get-next-equest при помощи фрагмента 127.0.1 (индекс) ответа ip. ipRouteTable. ipRouteEntry. ipRouteDest. 127. 0.0.1;

$ snmpgetnext gold public ip.ipRouteTable. ipRouteEntry. ipRouteDest, 127.0.0.1
ip.ipRouteTable.ipRouteEntry.ipRouteNextHop. 127.0.0.1

ip. ipRouteTable. ipRouteEnfy. ioRouteDest 192,168.1 = IpAadress: 192.168 " "
ip.ipRouteTable,ipRouteEntry.ipRouteNextHop.192,168.I1.0 = loAddress:
192.168.1.139

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

$ snmpgetnext gold public

ip.ipRouteTable.ipRouteEntry.ipfiouteDest.192.168.1.0

ip.ipRouteTable.ipRouteEntry.ipRouteNextHop. 192.168.1.0

ip.ipRouteTable.ipRouteEntry.ipRoutelflndex.0.0.0.0 = 1

ip.ipRouteTable.ipRouteEntry.ipRouteType.0.0.0.0 = indirect(4)

Стоп, ответ не совпадает с запросом! Ведь запрашивали ipRouteDest и ipRouteNextHop, а получили ipRoutelflndex и ipRouteType. Мы вышли за пределы таблицы ipRouteTable. SNMP-запрос get-next-request честно выполнил свой долг и вернул «первого лексикографического последователя» (first lexicographic successor) из MIB для каждого объекта из запроса. Если посмотреть в определение ipRouteEntry из RFC1213, то можно увидеть, что ipRouteIf!ndex(2) следует за ipRouteDest(l), a ipRou-teType(8) действительно следует за ipRouteNextHop(7).

Так что ответ на вопрос, заданный минуту назад: «Как узнать, что мы получили все данные из таблицы?», будет таким: «Когда вы заметите, что вышли за пределы таблицы». С точки зрения программирования
это равнозначно тому, чтобы проверить, возвращается ли в ответ на ваш запрос одна и та же строка или префикс OID. Например, можно убедиться, что все ответы на запрос о ipRouteDest содержали либо
ip. ipRouteTable. ipRouteEntry. ipRouteDest, либо 1. 3. 6.1. 2.1.4. 21.1.1.

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