В прошлой заметке Системы мониторинга, SNMPv3 и engineID я опубликовал перевод статьи Михаэля Шварцкопфа Monitoring Systems, SNMPv3 and the engineID. В этой заметке я поделюсь своим пониманием проблемы, с которой столкнулся автор, а затем расскажу о своём опыте решения подобных проблем с коммутаторами D-Link.
В SNMPv3 у каждого агента имеется свой уникальный идентификатор. При первом обращении к агенту станция управления выполняет так называемую процедуру обнаружения - отправляет ему пустой запрос. Агент отвечает на этот запрос, сообщая свой уникальный идентификатор - EngineID, количество перезагрузок агента - EngineBoots и время, прошедшее с момента последней перезагрузки агента - BootTime. Станция управления запоминает количество перезагрузок агента - EngineBoots и время, прошедшее с момента последней перезагрузки агента - EngineTime, в специальном кэше. В качестве ключа для поиска по этому кэшу используется идентификатор агента - EngineID.
Этот кэш нужен во-первых для того, чтобы уменьшить количество паразитных процедур обнаружения. В следующий раз можно будет взять хранящиеся в кэше значения, посчитать текущее время на агенте и отправить ему запрос. Во-вторых, приходящие ответы будут проверяться на соответствие значениям, хранящимся в кэше. Это позволяет защититься от атак повторного воспроизведения запросов, когда некто может попытаться сохранить запрос от системы управления и отправить агенту этот запрос позже. Было бы неприятно, если бы кто-то смог перехватить, например, команду SNMP SET, которая отключает порт на коммутаторе, и потом мог бы применять её впоследствии в любое время и любое количество раз. Проверка своевременности не позволит такому злоумышленнику развлекаться этой командой дольше 150 секунд.
Что произойдёт, если у нескольких устройств настроены одинаковые EngineID? В кэше будут сохраняться значения EngineBoots и EngineTime от одного устройства, а потом эти же значения будут использоваться для отправки запросов к другому устройству. Другое устройство обнаружит, что значения EngineBoots и EngineTime из полученного им запроса не его, и в соответствии с принципами защиты от атак повторного воспроизведения запросов отправит в ответ OID .1.3.6.1.6.3.15.1.1.2.0 - usmStatsNotInTimeWindows, сообщающий о том, что время из запроса не попало в приемлемое окно времени. Станция управления выполнит процедуру обнаружения и если EngineBoots из ответа второго устройства оказалось больше, то обновит значения в кэше. Если EngineBoots окажется равным хранящемуся в кэше, то сравнит EngineTime из кэша и из ответа - если EngineTime окажется больше, то обновит значение в кэше. Теперь на запросы будет отвечать только то устройство, которое больше раз перезагружалось или, при равных значениях, у которого с момента загрузки прошло больше времени. На всех остальных устройствах будет постоянно срабатывать защита от повторного воспроизведения запросов.
В случае Zabbix ситуация немного усугубляется ещё и тем, что библиотека NET-SNMP - однопоточная и не предусматривает возможность сохранения и восстановления контекста библиотеки. Возможно именно поэтому Zabbix был сделан не многопоточным, а многопроцессным. В результате у каждого процесса Poller, занимающегося пассивными проверками, имеется собственный кэш библиотеки SNMP. Каждый процесс поэтому выполняет собственную процедуру обнаружения, хотя в одном из соседних процессов в кэше уже могут быть нужные актуальные данные.
Поэтому не надо удивляться тому, что утилиты командной строки snmpget и snmpwalk работают без ошибок при каждом запуске, даже если у двух устройств одинаковый EngineID. Эти утилиты при каждом запуске выполняют процедуру обнаружения, наполняют кэш полученными значениями, выполняют запрос и тут же завершаются. При завершении утилиты пропадает и её кэш.
Проблема автора, видимо, заключалась в том, что у него на двух разных устройствах оказался одинаковый EngineID. После указания опции engineIDType 3 на устройствах были установлены EngineID, сгенерированные из MAC-адресов устройств. MAC-адреса должны быть уникальными (хотя на практике иногда случаются исключения), поэтому опция решила проблему. Приведённый же в статье протокол сниффера скорее всего отобразил два разных запроса от двух разных процессов Poller, с разными кэшами библиотеки SNMP. Один процесс имел пустой кэш и отправил запрос обнаружения, а второй процесс уже обладал ранее наполненным кэшем и отправил запрос используя старые значения из кэша. Автор же статьи, скорее всего, просто не разобрался в происходящем. Во всяком случае то, что проблема исправилась после переназначения EngineID свидетельствует именно в пользу версии о двух одинаковых EngineID на разных устройствах.
Лично мне пришлось столкнуться с проблемой одинаковых EngineID на коммутаторах. Дело в том, что идентификатор сохраняется в файле конфигурации коммутатора. Если при настройке нового коммутатора за основу взять файл конфигурации с имеющегося коммутатора, то EngineID продублируется. Для просмотра текущего значения EngineID на коммутаторах D-Link можно воспользоваться такой командой:
show snmp engineID
Для замены EngineID на коммутаторах D-Link используется такая команда:
config snmp engineID <snmp_engineID 10-64>
Некоторые модели коммутаторов D-Link поддерживают такую команду:
config snmp engineID default
Наконец, после замены нужно сохранить изменённый файл конфигурации:
save config
Если посмотреть в RFC5343, 4. IANA Considerations, то можно прочитать, по какому принципу следует формировать EngineID из MAC-адреса. Чтобы сформировать EngineID на основе MAC-адреса, нам потребуется дополнительно IANA-номер, закреплённый за производителем устройств. Определить номер IANA можно запросив у устройства OID SNMPv2-MIB::sysObjectID.0 - 1.3.6.1.2.1.1.2.0 при помощи такой команды:
$ snmpget -On -v 2c -c public 192.168.0.2 1.3.6.1.2.1.1.2.0 .1.3.6.1.2.1.1.2.0 = OID: .1.3.6.1.4.1.171.10.101.1
В выводе команды я подсветил IANA-номер, закреплённый за производителем D-Link. Если перевести число 171 в шестнадцатеричную систему счисления, то получим число AB. Первые четыре октета представляют собой именно это число, но с единичным старшим битом:
80 00 00 AB
Дальше следует один октет, в котором закодирован тип остальной части идентификатора. Если EngineID формируется на основе MAC-адреса, то этот октет будет иметь такое значение:
03
Наконец, оставшиеся 6 октетов будут соответствовать октетам MAC-адреса. Например:
28 10 7B 01 2B 7B
Итак, коммутатору D-Link с MAC-адресом 28:10:7B:01:2B:7B будет соответствовать следующий EngineID:
80 00 00 AB 03 28 10 7B 01 2B 7B
Для диагностики подобных проблем можно просмотреть ветку 1.3.6.1.6.3.10.2.1:
$ snmpwalk -v 2c -c public 127.0.0.1 1.3.6.1.6.3.10.2.1 SNMP-FRAMEWORK-MIB::snmpEngineID.0 = Hex-STRING: 80 00 1F 88 80 F3 F2 FA 22 48 AF 91 54 00 00 00 00 SNMP-FRAMEWORK-MIB::snmpEngineBoots.0 = INTEGER: 72 SNMP-FRAMEWORK-MIB::snmpEngineTime.0 = INTEGER: 6 seconds SNMP-FRAMEWORK-MIB::snmpEngineMaxMessageSize.0 = INTEGER: 1500
В выводе команды можно увидеть текущие значения EngineID, EngineBoots и EngineTime.
Вот так поменялся EngineID после добавления опции engineIDType 3 в /etc/snmp/snmpd.conf:
$ snmpwalk -v 2c -c public 127.0.0.1 1.3.6.1.6.3.10.2.1 SNMP-FRAMEWORK-MIB::snmpEngineID.0 = Hex-STRING: 80 00 1F 88 03 00 1D 60 01 2B 7B SNMP-FRAMEWORK-MIB::snmpEngineBoots.0 = INTEGER: 1 SNMP-FRAMEWORK-MIB::snmpEngineTime.0 = INTEGER: 3 seconds SNMP-FRAMEWORK-MIB::snmpEngineMaxMessageSize.0 = INTEGER: 1500
Как можно заметить, количество загрузок агента EngineBoots уменьшилось - это произошло потому что изменился EngineID и отсчёт был начат с начала.
Дополнительно в диагностике может помочь ветка 1.3.6.1.6.3.15.1.1:
$ snmpwalk -v 2c -c public 127.0.0.1 1.3.6.1.6.3.15.1.1 SNMP-USER-BASED-SM-MIB::usmStatsUnsupportedSecLevels.0 = Counter32: 0 SNMP-USER-BASED-SM-MIB::usmStatsNotInTimeWindows.0 = Counter32: 0 SNMP-USER-BASED-SM-MIB::usmStatsUnknownUserNames.0 = Counter32: 0 SNMP-USER-BASED-SM-MIB::usmStatsUnknownEngineIDs.0 = Counter32: 0 SNMP-USER-BASED-SM-MIB::usmStatsWrongDigests.0 = Counter32: 0 SNMP-USER-BASED-SM-MIB::usmStatsDecryptionErrors.0 = Counter32: 0
Здесь можно увидеть, сколько раз к агенту обращались с неподдерживаемым уровнем безопасности, в скольких запросах встречалось неприемлемое время, сколько раз обращались с неизвестными именами пользователя или чужими значениями EngineID, сколько раз происходили ошибки аутентификации и сколько раз не удавалось расшифровать данные из запроса.
В следующей заметке я расскажу о более глубоких проблемах, возникших при внедрении SNMPv3.