Показаны сообщения с ярлыком patch. Показать все сообщения
Показаны сообщения с ярлыком patch. Показать все сообщения

воскресенье, 26 марта 2017 г.

Таймаут DNS в OpenNTPd

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

Стал разбираться, что же не так. Как оказалось, загрузка остановилась на этапе запуска OpenNTPd. Заметил, что сервер не был подключен к сети. Воткнул сеть - сервер стал загружаться дальше. В интернете нашёл описание этой ошибки в OpenNTPd: net-misc/openntpd - failed dns results in extended startup delay when -s option in use

Проблема заключается в том, что у OpenNTPd предусмотрен таймаут для NTP-серверов, но не предусмотрен таймаут для DNS-серверов, поэтому демон продолжает в бесконечном цикле выполнять DNS-запросы, пытаясь узнать имена NTP-серверов.

Проблему можно решить одним из способов:
  • Убрать из настроек опцию -s,
  • Прописать в конфигурацию IP-адреса NTP-серверов, а не доменные имена,
  • Пропатчить пакет патчем из этого отчёта об ошибке.
Я решил тогда пропатчить пакет. Потом я много раз пользовался этим пакетом, но описать его сборку всё забывал. Вот сейчас решил всё-таки задокументировать эту процедуру, хотя в новых версиях Debian она станет больше не нужной, т.к. в новых версиях OpenNTPd ошибка уже исправлена.

Для начала отредактируем файл /etc/apt/sources.list и добавим строчки с репозиториями deb-src с исходными текстами:
deb http://mirror.ufanet.ru/debian/ wheezy main contrib non-free
deb http://mirror.ufanet.ru/debian/ wheezy-updates main contrib non-free
deb http://mirror.ufanet.ru/debian/ wheezy-proposed-updates main contrib non-free
deb http://mirror.ufanet.ru/debian-security wheezy/updates main contrib non-free

deb-src http://mirror.ufanet.ru/debian/ wheezy main contrib non-free
deb-src http://mirror.ufanet.ru/debian/ wheezy-updates main contrib non-free
deb-src http://mirror.ufanet.ru/debian/ wheezy-proposed-updates main contrib non-free
deb-src http://mirror.ufanet.ru/debian-security wheezy/updates main contrib non-free
Установим инструменты, необходимые для сборки пакета:
# apt-get install dpkg-dev devscripts fakeroot
Установим пакет с исходными текстами openntpd:
# apt-get source openntpd
Скачаем подготовленный мной патч:
# wget http://stupin.su/files/openntpd-20080406p-dns-timeout.patch
Перейдём в каталог с исходными текстами пакета:
# cd openntpd-20080406p
Наложим скачанный патч:
# patch -Np0 < ../openntpd-20080406p-dns-timeout.patch
Установим пакеты, необходимые для сборки пакета openntpd:
# apt-get build-dep openntpd
Оформим изменения, сделанные в исходных текстах, в виде патча:
# dpkg-source --commit
В ответ на запрос имени заплатки введём dns-timeout.

Содержимое заголовка заплатки:
Description: DNS timeout added
 Added patch from https://bugs.gentoo.org/show_bug.cgi?id=493358
 .
 openntpd (20080406p-4) unstable; urgency=low
Author: Vladimir Stupin <vladimir@stupin.su>
Last-Update: <2017-02-09>

--- openntpd-20080406p.orig/ntpd.c
+++ openntpd-20080406p/ntpd.c
Теперь опишем сделанные изменения в журнале изменений:
# dch -i
Свежая запись в журнале будет выглядеть следующим образом:
openntpd (20080406p-4.1) UNRELEASED; urgency=low

  * DNS timeout added

 -- Vladimir Stupin <vladimir@stupin.su>  Thu, 09 Mar 2017 22:11:37 +0500
Соберём новый пакет с исходными текстами и двоичный пакет:
# dpkg-buildpackage -us -uc -rfakeroot
Теперь можно перейти на уровень выше и установить собранный пакет:
# cd ..
# dpkg -i openntpd_20080406p-4.1_amd64.deb
С новым пакетом пауза в процессе загрузки составляет 1 минуту 40 секунд, что вполне приемлемо, т.к. сервер в конце концов всё-таки загружается, а на фоне всего процесса загрузки эта дополнительная пауза почти не заметна.

воскресенье, 9 октября 2016 г.

Хакинг TV Motion Detector VG

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

Для начала несколько слов о самой программе. Программа предназначена для мониторинга аналоговых телеканалов и, насколько я могу судить, довольно популярна у операторов кабельного телевидения. Программа поочерёдно переключает каналы на ТВ-тюнере и проверяет, есть ли на канале звук, двигается ли изображение. Программа умеет вызывать другие программы в случае если проблема была только что обнаружена, обнаружена повторно или произошло восстановление вещания. Подробнее о TV Motion Detector VG можно почитать на официальном сайте. Ещё один немаловажный момент - программа бесплатная. Пусть это будет ещё одним аргументом в пользу того, что эта заметка написана не за деньги и не является скрытой рекламой :)

TV Motion Detector VG использует файл scanfreq.txt со списком частот каналов и файл channels.sqlite со списком каналов в таблице channels, журналом событий в таблице log и списком запланированных задач в таблице scheduler. Имеется база данных, в которой инженеры головной станции поддерживают актуальный список каналов с их номерами, частотами и названиями. Специальная самописная программа запрашивает список каналов из базы данных, наполняемой инженерами головной станции, формирует эти два файла и через Zabbix API создаёт нужные элементы данных и триггеры для каждого канала. В сформированном файле channels.sqlite в таблице scheduler создаются записи, которые предписывают каждый час вызывать bat-файл, который завершает TV Motion Detector VG, вызывает программу, обновляющую Zabbix и эти два файла, а потом запускает TV Motion Detector VG снова. В таблице channels из файла channels.sqlite для каждого канала указываются скрипты, которые надо вызывать в случае проблем на канале, в случае продолжающихся проблем и в случае восстановления канала. В качестве таких скриптов используется утилита zabbix_sender. Программа TV Motion Detector VG прописана в автозагрузку Windows. В Zabbix кроме состояния каналов контролируется ещё и момент, когда в последний раз приходили данные от TV Motion Detector VG. Таким образом получаем практически полностью автономно работающую систему из трёх звеньев - базы каналов, TV Motion Detector VG и Zabbix.

Ручное вмешательство бывает нужно, если компьютер с TV Motion Detector VG был перезагружен. В этом случае в Zabbix срабатывает триггер о том, что давно не поступали данные из TV Motion Detector VG. Дежурный инженер заходит на компьютер с Windows по RDP, TV Motion Detector VG автоматически запускается, а дежурный инженер отсоединяется от сеанса. Дальше всё продолжает работать.

Бывает, что требуется ручное вмешательство другого рода. Если в частотном плане появляются новые частоты или пропадают старые, то TV Motion Detector VG (или драйвер ТВ-тюнера) сходит с ума и реально настраивается не на те каналы, которые соответствуют частоте канала в таблице channels из файла channels.sqlite. В таком случае помогает простое лечение - нажать кнопку Scan, дождаться окончания сканирования, после чего можно обновить файлы channels.sqlite, scanfreq.txt и запустить TV Motion Detector VG снова.

Вмешательства такого рода требуются довольно редко. А вот следующий недостаток изрядно подрывает всю кропотливо выстроенную систему.


Если приглядеться, то в нижней строчке под списком каналов программы можно увидеть текст "count: 50". Дело в том, что после перезапуска программа считает, что есть только 50 каналов. Только по ним она и проходится циклически, оставляя за пределами контроля ещё 11 каналов. Если вручную потянуть линейку прокрутки в списке каналов до конца, то программа отображает остальные каналы и после этого уже начинает проходиться циклически по каждому из 61 каналов. Но, напомню, программа в описанной выше схеме перезапускается каждый час. Поэтому ручное вмешательство не годится.

Первым делом я подумал, что программа при запуске выполняет SQL-запрос на получение списка каналов с ограничением LIMIT 50. Я открыл EXE-файл в шестнадцатеричном редакторе и нашёл все запросы SELECT. Был среди них такой запрос "SELECT COUNT(inc) as countinc, MAX(number) as maxnumber FROM channels;". Я проверил, что он возвращает и убедился, что обнаруживается ровно 61 канал.

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

Снова попробовал поискать что-нибудь в шестнадцатеричном редакторе. Естественно, таких мест оказалось довольно много. Не знаю, что натолкнуло меня на мысль попробовать ArtMoney, потому что изначально было ясно, что даже если я и найду эти счётчики в оперативной памяти, то как потом мне найти и исправить их в файле EXE? Если они не хранятся в секции .BSS или подобной ей, то найти нужные константы в ассемблерном коде будет непросто. Да и даже если они хранятся в такой секции - у меня нет опыта правки EXE-файлов. Всё же решил для любопытства найти этот счётчик при помощи ArtMoney.

Попытка поиска увенчалась успехом - я нашёл аж две ячейки памяти, в которых хранились 32-битные числа, соответствовавшие количеству каналов в списке. Попытки исправить это число на 61 давали эффект ровно до того момента, когда программа заканчивала просмотр пятидесятого канала и пыталась приступить к следующему. Дальше программа ежесекундно выбрасывала окошки с сообщениями об ошибке до тех пор, пока я не возвращал в обе ячейки прежнее число 50.

Потом я сделал перерыв на обед. Пока шёл в столовую, подумал что свойства компонентов на форме могут оказаться прописаны в ресурсах Windows, вшитых в EXE-файл. Когда вернулся с обеда, решил проверить это предположение. В прошлом я доставал из EXE- и DLL-файлов встроенные в них пиктограммы, курсоры и картинки с помощью программы ResEdit. Скачал программу с официальной страницы. Из всех ресурсов наиболее многообещающим выглядел ресурс с именем TFORMMAIN. Но ResEdit отображал этот ресурс как блок двоичных данных.

Интересующий меня ресурс начинался с сигнатуры TPF0. Я поискал в интернете и понял, что это формы Delphi. В проекте Delphi у файлов этих форм бывает расширение DFM. Решил поискать, чем можно открыть такой файл на просмотр и редактирование. Вышел на программу ResourceHacker, которую можно скачать на странице разработчика - Ангуса Джонсона. Скачал программу и открыл интересующий меня ресурс на просмотр и редактирование. Стал изучать, какие компоненты присутствуют на форме и какие их свойства можно было бы поправить.

В процессе изучения, сопровождавшегося поиском в интернете, понял, что программа для доступа к SQLite использует компоненты AnyDAC Дмитрия Арефьева. К сожалению, документацию на AnyDAC найти в Интернете мне не удалось. Дело в том, что AnyDAC была продана фирме Embarcadero и превратилась в FireDAC. Удалось найти документацию только на библиотеку FireDAC. Оставалась слабая надежда на то, что библиотека изменилась не очень сильно и опции из FireDAC подойдут для AnyDAC.

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

Верное направление для поисков было нащупано в ветке форума на www.sql.ru, где обсуждался FireDAC. Список всех возможных значений нашёлся в статье документации FireDAC.Stan.Option.TFDFetchOptions.RecordCountMode. На каком-то этапе я попробовал вставить в описание объекта ADTableChannels после 1738 строчки декомпилированного ресурса TFORMMAIN свойство FetchOptions.RecordCountMode = cmTotal:

Затем скомпилировал изменения нажав на зелёный треугольник и перезаписал файл EXE. Когда запустил программу, то увидел в строчке count вожделенное значение 61. Оставалось убедиться, что после 50 канала программа переключится на 51 канал и дойдёт до конца списка. Всё сработало, как надо:

В этот момент я испытал облегчение, радость и удивление от того, что поиски и эксперименты в конечном итоге привели к успеху. С одной стороны, я не надеялся на успех. Уж слишком были призрачными надежды на него. У читателей этого блога может сложиться впечатление, что все задачи, которые я перед собой ставлю, в конечном итоге завершаются успешно. Однако это не всегда бывает так. Иногда эксперименты заходят в тупик и очередная задача так и остаётся нерешённой. Раз нет решения, то и писать не о чем. С другой стороны, пока голова не перестала генерировать новые предположения и пока они все не проверены, остаётся надежда на успех. В этот раз мне повезло :)

P.S. Несколько замечаний о законности описанной выше модификации программы.
УК РФ, Статья 272. Неправомерный доступ к компьютерной информации.

(в ред. Федерального закона от 07.12.2011 N 420-ФЗ)

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

наказывается штрафом в размере до двухсот тысяч рублей или в размере заработной платы или иного дохода осужденного за период до восемнадцати месяцев, либо исправительными работами на срок до одного года, либо ограничением свободы на срок до двух лет, либо принудительными работами на срок до двух лет, либо лишением свободы на тот же срок.
Источник - сайт компании "КонсультантПлюс". УК РФ, Статья 272. Неправомерный доступ к компьютерной информации

Однако, в комментариях к УК РФ имеются следующие оговорки:
1. Модификация информации - внесение изменений в компьютерную информацию (в том числе изменение её параметров) без согласия её законного обладателя. Действующим законодатесльством разрешены следующие виды легальной модификации программ, баз данных лицами, правомерно владеющими этой информацией: а) модификация в виде исправления явных ошибок; б) модификация в виде внесения изменений в программы, базы данных для их функционирования на технических средствах пользователя; в) модификация в виде частной декомпиляции программы для достижения способности к взаимодействию с другими программами.
Источник - книга "Комментарий к Уголовному кодексу Российской Федерации (постатейный), Том 2" под редакцией Бриллиантова А.В. Глава 28. Преступления в сфере компьютерной информации. Статья 272. Неправомерный доступ к компьютерной информации

Ещё один источник - сайт адвокатского бюро "Домкины и партнёры". Методические рекомендации по осуществлению прокурорского надзора за исполнением законов при расследовании преступлений в сфере компьютерной информации

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

воскресенье, 25 сентября 2016 г.

Стандарт SNMPv3 и суровая действительность USM_TIME_WINDOW

В прошлой статье Zabbix и проблемы с опросом по SNMPv3 при дублировании EngineID я описал решение первой проблемы, возникшей при внедрении SNMP третьей версии. После этого я столкнулся с ещё одной проблемой, которую исправлял в два подхода.

D-Link не соблюдает стандарт RFC3414

Дело в том, что даже после исправления EngineID на всех коммутаторах, на некоторых коммутаторах проблема повторялась. После перезагрузки коммутатора, Zabbix считал коммутатор недоступным по SNMP. Если разбираться глубже, с помощью приведённых в прошлой заметке OID'ов, то оказывается, что на коммутаторе после перезагрузки значение EngineBoots остаётся прежним или даже уменьшается! Первым делом были проверены версии прошивок - возможно эта проблема уже была исправлена и надо только обновить прошивку. Но оказалось, что прошивки на проблемных коммутаторах были самые свежие. Поискал в интернете, сталкивался ли кто-нибудь с подобной проблемой, и нашёл аж три темы, в которых открытым текстом утверждается, что эта проблема известна производителю и исправлять он её не собирается:
Поразмыслив, я решил, что можно было бы отключить проверку монотонности возрастания EngineBoots. В конце концов, лучше использовать SNMP третьей версии без защиты от повторного воспроизведения запросов, но зато с более безопасной аутентификацией и с защитой информации от подслушивания, чем вернуться к использованию SNMP второй или первой версии и передавать по сети в открытом виде и строку сообщества и сами запросы и ответы. Решено - будем править библиотеку.

Подготовка к пересборке Net-SNMP

Для начала отредактируем файл /etc/apt/sources.list и добавим строчки с репозиториями deb-src с исходными текстами:
deb http://mirror.ufanet.ru/debian/ wheezy main contrib non-free
deb http://mirror.ufanet.ru/debian/ wheezy-updates main contrib non-free
deb http://mirror.ufanet.ru/debian/ wheezy-proposed-updates main contrib non-free
deb http://mirror.ufanet.ru/debian-security wheezy/updates main contrib non-free

deb-src http://mirror.ufanet.ru/debian/ wheezy main contrib non-free
deb-src http://mirror.ufanet.ru/debian/ wheezy-updates main contrib non-free
deb-src http://mirror.ufanet.ru/debian/ wheezy-proposed-updates main contrib non-free
deb-src http://mirror.ufanet.ru/debian-security wheezy/updates main contrib non-free
Обновим список доступных пакетов, чтобы можно было скачать из вновь подключенных репозиториев пакеты с исходными текстами:
# apt-get update
Скачаем и распакуем исходные тексты, а затем перейдём в каталог с распакованными исходными текстами:
$ apt-get source snmp
$ cd net-snmp-5.4.3~dfsg/
Описание исправлений, внесённых в Net-SNMP

Поиск показал, что константа USM_TIME_WINDOW со значением 150 используется только в файле snmplib/snmpusm.c. Первым делом я нашёл строчку, в которой проверяется, что значение EngineBoots должно быть больше или равно значению из кэша:
if (theirBoots == ENGINEBOOT_MAX || theirBoots > boots_uint) {
Если значение EngineBoots не изменилось, то используются значение EngineTime, сохранённое в кэше. Если значение стало больше, то новые значения EngineBoots и EngineTime сохраняются в кэш. Если значение уменьшилось, то запрос завершается ошибкой SNMPERR_USM_NOTINTIMEWINDOW.

Это строчку я исправил следующим образом:
if (theirBoots == ENGINEBOOT_MAX) {
То есть при любом изменении значения EngineBoots новое значение будет сохранено в кэш для дальнейшего использования.

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

Детальное изучение проблемы показало, что при перезагрузке значение EngineBoots у них не изменилось, зато EngineTime становилось меньше прошлого, т.к. отсчёт времени после перезагрузки начинался с нуля. В этом случае вступал в силу другой фрагмент кода, который при равенстве полученного значения EngineBoots и имеющегося в кэше проверяет соответствие времени. Если время из ответа больше расчётного, то библиотека запоминает новое значение времени в кэше и принимает ответ. Если время из ответа отстаёт от расчётного менее чем на 150 секунд, то такой ответ тоже принимается. Если же время из ответа отстаёт от расчётного более чем на 150 секунд, то ответ отбрасывается, а запрос завершается всё той же ошибкой SNMPERR_USM_NOTINTIMEWINDOW. Этот фрагмент кода выглядит следующим образом:
/*
 * Boots is ok, see if the boots is the same but the time
 * is old.
 */
if (theirBoots == boots_uint && time_uint < theirLastTime) {
        if (time_difference > USM_TIME_WINDOW) {
                DEBUGMSGTL(("usm", "%s\n", "Message too old."));
                *error = SNMPERR_USM_NOTINTIMEWINDOW;
                return -1;
        }
        else {              /* Old, but acceptable */
                *error = SNMPERR_SUCCESS;
                return 0;
        }
}
Его я исправил следующим образом:
/*
 * Boots is ok, see if the boots is the same but the time
 * is acceptable old.
 */
if (theirBoots == boots_uint && time_uint < theirLastTime && time_difference < USM_TIME_WINDOW) {
        *error = SNMPERR_SUCCESS;
        return 0;
}
Снова собрал пакет, установил его в систему и перезапустил Zabbix. Через несколько дней стало понятно, что это исправление помогло и опрос по SNMP третьей версии теперь продолжает работать даже после перезагрузок коммутаторов, когда значение EngineBoots не меняется или меняется в меньшую сторону. Был достигнут неприятный компромисс, на который пришлось пойти ради общего увеличения безопасности системы.

Сборка исправленных пакетов Net-SNMP

Теперь уже, когда в деталях раскрыта подноготная исправлений, опишу до конца процесс сборки пакета.

Скачиваем и устанавливаем пакеты, которые используются в процессе сборки Net-SNMP:
# apt-get build-dep snmp
Скачиваем патч:
$ wget http://stupin.su/files/netsnmp543_snmpv3.diff
Тут же, если вы используете библиотеку Net-SNMP из Python, можно скачать ещё один патч, описанный в заметке Исправление Python-прослойки библиотеки Net-SNMP:
$ wget http://stupin.su/files/netsnmp543_python_client.diff
Теперь перейдём в каталог с исходными текстами и наложим наши патчи:
# cd net-snmp-5.4.3~dfsg/
# patch -Np0 --ignore-whitespace < netsnmp543_snmpv3.diff
# patch -Np0 --ignore-whitespace < netsnmp543_python_client.diff
Добавим наши патчи в будущий пакет:
# dpkg-source --commit
Укажем имя патча snmpv3-python_client. В окне редактирования информации о патче введём что-нибудь подобное следующему (вплоть до двух нижних строчек, с которых начинается сам текст патча):
Description: Fixed Python bindings and SNMPv3 time window logic
Author: Vladimir Stupin <vladimir@stupin.su>
Last-Update: <2016-09-06>
--- net-snmp-5.4.3~dfsg.orig/python/netsnmp/client_intf.c
+++ net-snmp-5.4.3~dfsg/python/netsnmp/client_intf.c
Теперь отметим изменения, внесённые в пакет:
# dch -i
В окне редактирования введём информацию о нашей версии пакета:
net-snmp (5.4.3~dfsg-2.8+deb7u2) UNRELEASED; urgency=low

* Fixed Python bindings for IPv4 parameter in SET-requests
* Fixed SNMPv3 time window logic

-- Vladimir Stupin <vladimir@stupin.su>  Tue, 06 Sep 2016 16:01:20 +0500
Осталось собрать пакеты:
# dpkg-buildpackage -us -uc -rfakeroot
Теперь можно установить необходимые пакеты в систему:
# cd ..
# dpkg -i libsnmp-base_5.4.3~dfsg-2.8+deb7u2_amd64.deb
# dpkg -i libsnmp15_5.4.3~dfsg-2.8+deb7u2_amd64.deb
# dpkg -i snmp_5.4.3~dfsg-2.8+deb7u2_amd64.deb
# dpkg -i libsnmp-python_5.4.3~dfsg-2.8+deb7u2_amd64.deb
Python и SNMPv3 в Net-SNMP

Двух пересборок пакетов, на самом деле, оказалось недостаточно. Дело в том, что модуль netsnmp для Python не работает с SNMP третьей версии. Как оказалось, при попытке выполнить из программы на Python запроса SNMP с настройками третьей версии, библиотека "молчит" - в сеть не уходит ни единый пакет. В то же время я вспомнил, что на одном из серверов под управлением FreeBSD всё работало превосходно. Сразу же в глаза бросилось различие в версиях Net-SNMP во FreeBSD на том сервере и на сервере под управлением Debian Wheezy. Во FreeBSD был установлен Net-SNMP версии 5.7.2, а в Debian - 5.4.3. Чтобы проверить свои предположения о том, что этот функционал был исправлен в версии 5.7.2, я воспользовался Net-SNMP из Debian Jessie. Набросал небольшую тестовую программку на Python, запустил - всё отработало на ура.

После этого я заменил в файле /etc/apt/sources.list репозитории deb-src с релиза Wheezy на релиз Jessie и повторил проделанные выше операции, получив таким образом пропатченный бэкпорт Net-SNMP из Jessie для Wheezy. Дело, однако, этим не ограничилось. В бэкпорте вместо библиотеки libsnmp15 была библиотека libsnmp30, а пакет libsnmp-python был переименован в python-netsnmp. К библиотеке libsnmp15 были привязаны Zabbix и PHP5. Пришлось пересобрать и их, в том числе с использованием моих патчей Zabbix, описанных в заметке Установка и настройка Zabbix 2.2.0 в Debian Wheezy.

Патчи, адаптированные для Net-SNMP версии 5.7.2.1 из Jessie, можно взять по следующим ссылкам:
Мне невероятно "везёт" с программами, которыми я пользуюсь. Или просто я слишком часто стал заходить на никем ещё не топтанные дорожки?

воскресенье, 14 февраля 2016 г.

Исправление Python-прослойки библиотеки Net-SNMP во FreeBSD

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

Ниже описано, как я исправил отправку параметров типа IPADDR в запросах SNMP SET в пакете net-snmp-5.7.2_2 во FreeBSD 8.2 своим патчем.

Перейдём в каталог порта:
# cd /usr/ports/net-mgmt/net-snmp
Создадим в каталоге порта files файл патча под именем patch-python__netsnmp__client_intf.c со следующим содержимым:
--- python/netsnmp/client_intf.c        2010-02-10 15:04:31.000000000 +0500
+++ python.new/netsnmp/client_intf.c    2014-12-21 18:10:26.677816256 +0500
@@ -979,14 +979,20 @@

       case TYPE_IPADDR:
         vars->type = ASN_IPADDRESS;
-        vars->val.integer = (long *)malloc(sizeof(long));
+        in_addr_t addr = 0;
         if (val)
-            *(vars->val.integer) = inet_addr(val);
+            addr = inet_addr(val);
+
+        if (addr && (addr != -1)) {
+            vars->val.string = malloc(sizeof(in_addr_t));
+            vars->val_len = sizeof(in_addr_t);
+            memcpy(vars->val.string, &addr, sizeof(in_addr_t));
+        }
         else {
             ret = FAILURE;
-            *(vars->val.integer) = 0;
+            vars->val.string = (u_char*)strdup("");
+            vars->val_len = 0;
         }
-        vars->val_len = sizeof(long);
         break;

       case TYPE_OBJID:
@@ -2426,6 +2432,7 @@
          goto done;
        }
        memcpy(tmp_val_str, val, tmplen);
+        tmp_val_str[tmplen] = '\0';
        if (type==TYPE_INTEGER && use_enums && tp && tp->enums) {
          for(ep = tp->enums; ep; ep = ep->next) {
            if (val && !strcmp(ep->label, val)) {
Теперь очистим порт и распакуем исходники, не накладывая никаких патчей:
# make clean
# make extract
Перейдём в каталог с распакованными исходными текстами:
# cd work/net-snmp-5.7.2/
Наложим имеющийся патч, игнорируя пробелы и несовпадение номеров строк, указанных в патче:
# patch -Np0 --ignore-whitespace < ../../files/patch-python__netsnmp__client_intf.c
Перейдём в каталог порта и пересоздадим файл патча files/patch-python__netsnmp__client_intf.c, в котором теперь пробелы и номера строк выставятся так, чтобы соответствовать исходникам из порта:
# cd ../..
# make makepatch
Очистим теперь файлы, созданные портом:
# make clean
Распакуем и наложим на исходники все патчи, включая наш:
# make patch
Если ошибок не было, можно собрать порт:
# make
Если порт собрался успешно, его можно установить:
# make install
Если порт ранее уже был установлен из той же версии портов, с которой мы только что работали, то можно безболезненно заменить файлы порта:
# make deinstall
# make install
Если же имеющийся в системе пакет был установлен из другой версии портов, тогда для обновления этого порта и всех зависящих от него рекомендую воспользоваться инструментом portupgrade. Рассмотрение этого инструмента я оставлю за рамками этой заметки.

воскресенье, 7 февраля 2016 г.

Исправление Python-прослойки библиотеки Net-SNMP

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

Из всех SNMP-модулей Python для работы с SNMP наименьшей гадостью мне показалась официальная прослойка библиотеки Net-SNMP для Python. Её разработчики не так прониклись Java-подходом, как разработчики библиотеки pysnmp, воспользоваться которой невозможно без написания трёхэтажного кода с использованием всех фабрик классов, прокси-классов и преобразования типов, родных для Python, в классы, используемые библиотекой. Библиотека pysnmp выглядит в Python совершенно чужеродно, потребляет мнго ресурсов и плохо документирована.

Прослойка библиотеки Net-SNMP для Python выглядит очень куцей, однако для моих нужд её оказывалось достаточно, пока я не попробовал с её помощью инициировать резервное копирование коммутатора. Оказалось, что в ней неправильно реализовано преобразование параметров типа IPADDR в запросах SNMP SET. Официальный патч в тот момент я не нашёл и решил попробовать исправить библиотеку самостоятельно, что мне и удалось. Официальный патч можно найти тут: Python bindings: Fix the out-of-bounds write. Я же подготовил свой патч, использование которого будет описано ниже.

Скачиваем патч:
$ wget http://stupin.su/files/netsnmp543_python_client.diff
Скачиваем и распаковываем в текущий каталог исходные тексты пакета:
# apt-get source libsnmp-python
Скачиваем и устанавливаем зависимости, необходимые для сборки пакета:
# apt-get build-dep net-snmp
Скачиваем и устанавливаем инструменты для сборки:
# apt-get install dpkg-dev
# apt-get install fakeroot
# apt-get install devscripts
Переходим в каталог с исходными текстами пакета и накладываем патч:
# cd net-snmp-5.4.3~dfsg/
# patch -Np0 --ignore-whitespace < ../netsnmp543_python_client.diff
Добавим наш патч в будущий пакет:
# dpkg-source --commit
Укажем имя патча snmp_set_fixed. В окне редактирования информации о патче введём что-нибудь подобное следующему (вплоть до двух нижних строчек, с которых начинается сам текст патча):
Description: Fixed snmp set operation in python-snmp
Fixed snmp set operation in python-snmp
 .
 net-snmp (5.4.3~dfsg-2.8+deb7u1) stable-proposed-updates; urgency=medium
 .
--- net-snmp-5.4.3~dfsg.orig/python/netsnmp/client_intf.c
+++ net-snmp-5.4.3~dfsg/python/netsnmp/client_intf.c
Теперь отметим изменения, внесённые в пакет:
# dch -i
В окне редактирования введём информацию о нашей версии пакета:
net-snmp (5.4.3~dfsg-2.8+deb7ufanet) UNRELEASED; urgency=low

  * Fixed snmp set in libsnmp-python

 -- Vladimir Stupin <vladimir@stupin.su>  Thu, 21 Jan 2016 16:58:40 +0500
Осталось собрать пакет:
# dpkg-buildpackage -us -uc -rfakeroot
Теперь его можно установить в систему:
# cd ..
# dpkg -i libsnmp-python_5.4.3~dfsg-2.8+deb7u1_amd64.deb

воскресенье, 27 сентября 2015 г.

NUT и APC Smart-UPS 1500VA

Благодаря Курбан-байраму на этой неделе появилось немного времени попробовать разобраться с ошибкой сегментации памяти в драйвере usbhid-ups из системы NUT.

Настройка драйвера NUT

Ставим nut-server и nut-client из репозитория:
# apt-get install nut-server nut-client
На странице руководства usbhid-ups описаны настройки, позволяющие привязать драйвер к строго определённому устройству. Можно указать настройки vendor, vendorid, product, productid, bus, serial.

Ищем идентификатор производителя и модели нашего ИБП при помощи lsusb. У меня ИБП в выводе этой программы выглядит так:
Bus 004 Device 006: ID 051d:0002 American Power Conversion Uninterruptible Power Supply
Теперь при помощи команды lsusb -vd 051d:0002 посмотрим другую информацию об ИБП. Я нашёл там серийный номер - он полезен, если к компьютеру подключено два ИБП одной и той же модели.

Используя полученную информацию, настроим драйвер в файле /etc/nut/ups.conf:
[apc1500]
        driver = usbhid-ups
        port = auto
        vendorid = 051d
        productid = 0002
        serial = AS0508120261
Теперь драйвер можно попробовать запустить вручную следующей командой:
# /lib/nut/usbhid-ups -a apc1500
В Debian Jessie программа завершается ошибкой сегментации. В Debian Stretch эта ошибка уже исправлена, поэтому в случае Debian Jessie можно сразу перейти к разделу "Исправление прав доступа к устройству" ниже.

Поиск неисправности в драйвере NUT

Найти ошибку мне удалось с помощью программы ltrace. Запустим драйвер под управлением этой программы:
# ltrace /lib/nut/usbhid-ups -a apc1500
Последние строчки вывода ltrace выглядят следующим образом:
strlen("Back-UPS ES 525")                                                                        = 15
strncmp(nil, "Back-UPS ES 525", 15 <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
Как видно, ошибка сегментации происходит потому, что происходит попытка сравнить строки. Первый аргумент функции сравнения строк strncmp имеет значение NULL. Используя это значение как указатель на область памяти, функция пытается обратиться по адресу, которого нет в адресном пространстве программы. Модуль управления памяти (MMU) генерирует прерывание о недоступности страницы, управление передаётся ядру операционной системы. Операционная система ищет затребованную память в каталоге страниц перемещённых в раздел подкачки и в списке файлов, отображаемых в память. Страницу не удаётся найти ни там ни там и ядро операционной системы завершает программу.

Исправление неисправности

Скачаем и распакуем исходные тексты системы NUT:
# cd /root
# apt-get source nut-server
Перейдём в каталог с распакованными файлами и поищем в них строку "Back-UPS ES 525":
# cd nut-2.7.2
# grep -R "Back-UPS ES 525" *
drivers/apc-hid.c: /* Back-UPS ES 525 overflows on ReportID 0x0c
drivers/apc-hid.c: "Back-UPS ES 525",
Строка используется в файле drivers/apc-hid.c. Откроем его и посмотрим, можно ли исправить проблему. Проблема находится в функции general_apc_check:
static void *general_apc_check(USBDevice_t *device)
{
        int i = 0;

        /* Some models of Back-UPS overflow on some ReportID.
         * This results in some data not being exposed and IO errors on
         * WIN32, causing endless reconnection or driver's failure */

        while( tweak_max_report[i] != NULL ) {
                if(!strncmp(device->Product, tweak_max_report[i],
                        strlen(tweak_max_report[i]))) {
                        max_report_size = 1;
                        return NULL;
                }
                i++;
        }
        return NULL;
}
То есть ошибка сегментации памяти происходит из-за попытки обратиться к области памяти, на которую указывает device->Product. Исправим это, пропуская цикл, если значение этого указателя равно NULL:
static void *general_apc_check(USBDevice_t *device)
{
        int i = 0;

        /* Some models of Back-UPS overflow on some ReportID.
         * This results in some data not being exposed and IO errors on
         * WIN32, causing endless reconnection or driver's failure */

        if (device->Product == NULL) {
                return NULL;
        }
        while( tweak_max_report[i] != NULL ) {
                if(!strncmp(device->Product, tweak_max_report[i],
                        strlen(tweak_max_report[i]))) {
                        max_report_size = 1;
                        return NULL;
                }
                i++;
        }
        return NULL;
}
Узаконим это изменение. Во-первых, внесём новую запись в changelog:
# dch -i
В открывшемся редакторе добавляем запись:
nut (2.7.2-4.1) UNRELEASED; urgency=medium

  * Fixed segmentation fault in function general_apc_check

 -- Vladimir Stupin <vladimir@stupin.su>  Thu, 24 Sep 2015 13:48:20 +0500

Теперь фиксируем изменения в исходном тексте при помощи команды:
# dpkg-source --commit
Патч назовём general_apc_check_segfault_fixed, в заголовок впишем комментарии к патчу:
Description: Fixed segmentation fault in function general_apc_check
 Fixed segmentation fault in function general_apc_check
 .
 nut (2.7.2-4.1) UNRELEASED; urgency=medium
 .
   * Fixed segmentation fault in function general_apc_check
Author: Vladimir Stupin <vladimir@stupin.su>

Осталось собрать новые пакеты. Установим зависимости, необходимые для сборки, а затем соберём пакеты:
# apt-get build-dep nut-server
# dpkg-buildpackage -us -uc -rfakeroot
В каталоге выше, на одном уровне с nut-2.7.2 появятся новые файлы:
libups-nut-perl_2.7.2-4.1_all.deb
nut_2.7.2-4.1_all.deb
nut_2.7.2-4.1_amd64.changes
nut_2.7.2-4.1.debian.tar.xz
nut_2.7.2-4.1.dsc
nut_2.7.2.orig.tar.gz
nut-cgi_2.7.2-4.1_amd64.deb
nut-client_2.7.2-4.1_amd64.deb
nut-doc_2.7.2-4.1_all.deb
nut-ipmi_2.7.2-4.1_amd64.deb
nut-monitor_2.7.2-4.1_all.deb
nut-powerman-pdu_2.7.2-4.1_amd64.deb
nut-server_2.7.2-4.1_amd64.deb
nut-snmp_2.7.2-4.1_amd64.deb
nut-xml_2.7.2-4.1_amd64.deb
python-nut_2.7.2-4.1_all.deb
Установим собранные нами пакеты nut-server и nut-client:
# dpkg -i nut-server_2.7.2-4.1_amd64.deb nut-client_2.7.2-4.1_amd64.deb
Попробуем запустить исправленный драйвер:
# /lib/nut/usbhid-ups -a apc1500
На этот раз ошибки сегментации нет, а драйвер самостоятельно завершает работу, сообщая об ошибке:
Can't claim USB device [051d:0002]: could not detach kernel driver from interface 0: Operation not permitted

Исправление прав доступа к устройству

Как выяснилось, дело в правах доступа к файлу устройства /dev/bus/usb/004/007, под которым доступен ИБП. К этому устройству должен иметь доступ пользователь или группа nut. Применим решение, найденное в обсуждении Trouble starting Network UPS Tools with a Eaton 3S UPS. Для этого создадим файл /etc/udev/rules.d/90-nut-ups.rules со следующим содержимым:
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="051d", ATTR{idProduct}=="0002", MODE="0660", GROUP="nut"
Чтобы настройки вступили в силу, перезагрузим udevd:
# /etc/init.d/udev reload
Теперь отсоединим USB-кабель, которым ИБП соединён с компьютером, и вставим его обратно. Права доступа должны исправиться.

Настройка сервера NUT

Откроем файл с настройками NUT-сервера /etc/nut/upsd.conf и впишем в него строчку:
LISTEN 127.0.0.1 3493
Откроем файл со списком пользователей NUT-сервера /etc/nut/upsd.users:
[admin]
        password = admin_password
        actions = SET
        instcmds = ALL

[upsmon]
        password = upsmon_password
        upsmon master
Откроем файл /etc/nut/nut.conf и заменим режим работы NUT-сервера с none на standalone:
MODE=standalone
Теперь включим и запустим NUT-сервер:
# systemctl enable nut-server.service
# systemctl start nut-server.service
Запустился ли NUT-сервер, можно посмотреть при помощи следующей команды:
# systemctl status nut-server.service
Настройка клиента мониторинга NUT

Простейший клиент позволяет просматривать текущие параметры ИБП:
$ upsc apc1500
Посмотреть значение какого-то одного конкретного параметра можно указав дополнительно его имя:
$ upsc apc1500 ups.beeper.status
Кроме того, имеется более сложный клиент, который позволяет не только просматривать настройки, но и выполнять команды. Список доступных команд можно увидеть вот так:
$ upscmd -l -u admin -p admin_password apc1500
Например, вот так можно отключить на ИБП звуковой сигнал:
$ upscmd -u admin -p admin_password apc1500 beeper.disable
Можно убедиться в том, что звуковой сигнал действительно отключен:
$ upsc apc1500 ups.beeper.status
Кроме поддержки моделей ИБП разных производителей, NUT выгодно отличается от apcupsd именно возможностью выполнять команды на ИБП. Можно, например, с помощью соответствующей команды протестировать всю процедуру отключения системы.

Однако, мы отклонились от темы. Настроим клиент мониторинга, который будет заниматься слежением за состоянием ИБП и правильным завершением работы при отсутствии электричества и полном разряде батарей. Откроем файл /etc/nut/upsmon.conf и впишем в него следующие настройки:
# Описание ИБП, за которым нужно наблюдать
MONITOR apc1500@localhost 1 upsmon upsmon_password master

# Команда, которую нужно выполнить перед пропаданием питания от ИБП
SHUTDOWNCMD "/sbin/shutdown -h +0"
Команда нужна именно такая, чтобы компьютер включился при подаче питания. Для этого в BIOS выставляется соответствующая настройка, которая либо всегда при появлении электричества подаёт его на блок питания, либо помнит последнее состояние и подаёт питание, если до пропадания электричества компьютер был включен. Если вам интересно узнать назначение остальных настроек, можете почитать одну из моих прошлых заметок NUT и Eaton Powerware 5110, в которой система NUT разобрана досконально.

Осталось включить и запустить клиента мониторинга NUT:
# systemctl enable ups-monitor.service
# systemctl start ups-monitor.service
Так же, как и в случае с NUT-сервером, можно проверить состояние клиента мониторинга:
# systemctl status ups-monitor.service
Настройка Zabbix

Предполагается, что на компьютере уже установлен и настроен Zabbix-агент. Добавим в конфигурацию агента "пользовательский параметр". Сделать этом можно либо напрямую отредактировав файл /etc/zabbix/zabbix_agentd.conf, либо создав новый файл в каталоге /etc/zabbix/zabbix_agentd.d/ специально для этого пользовательского параметра. Впишем строчку:
UserParameter=nut[*],/bin/upsc $1@$2 $3 2>/dev/null
Перезапустим Zabbix-агента, чтобы новые настройки вступили в силу:
# systemctl restart zabbix-agent.service
Я подготовил два варианта шаблонов - один с элементами данных "Zabbix-агент", а второй - с элементами данных "Zabbix-агент (активный)".

Поскольку к NUT можно подключить несколько ИБП, в шаблонах предусмотрены макросы, в которых указывается конкретный ИБП, данные с которого нужно снимать. Если ИБП один, можно присоединить шаблон прямо к узлу Zabbix, соответствующему компьютеру, к которому подключен ИБП. Если ИБП несколько, то удобнее будет создать отдельные узлы Zabbix, соответствующие каждому из наблюдаемых ИБП. Макросы узла Zabbix выглядят вот так:

Снимаемые с ИБП данные выглядят следующим образом:

Наконец, в шаблоне имеются следующие триггеры:

Дополнительно на узле Zabbix, соответствующем компьютеру, к которому подключены ИБП, можно поставить на контроль наличие работающих процессов upsd и upsmon.

Примеры использования графического клиента NUT и настройки веб-клиента NUT можно посмотреть в прошлой заметке, посвящённой настройке NUT: NUT и Eaton Powerware 5110.

воскресенье, 15 декабря 2013 г.

Установка и настройка Zabbix 2.2.0 в Debian Wheezy

Недавно вышел новый релиз Zabbix 2.2, в котором, по словам разработчиков, была существенна увеличена производительность, в 2-5 раз. Достигнуто это было, видимо, за счёт удаления столбцов lastvalue и prevvalue из таблицы items.

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

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

Другим важным для меня изменением стало изменение настроек опроса по SNMP. Раньше использовались настройки опроса по умолчанию, из библиотеки SNMP. По умолчанию таймаут опроса составлял 1 секунду, а в случае неудачи делалось до 5 дополнительных попыток опроса. В Zabbix 2.2 для опроса по SNMP используется значение настройки Timeout, заданное в файле конфигурации и делается только одна попытка опроса.

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

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

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

1. Исправление исходников и сборка deb-пакетов

Добавим официальные репозитории с файлами для сборки пакетов в файл /etc/apt/sources.list:
deb-src http://repo.zabbix.com/zabbix/2.2/debian wheezy main
Установим ключ репозитория Zabbix и обновим список доступных пакетов:
# wget http://repo.zabbix.com/zabbix-official-repo.key -O - | apt-key add -
# apt-get update
Установим необходимые для сборки пакетов Zabbix зависимости и скачаем файлы для сборки пакетов:
# apt-get build-dep zabbix
# apt-get source zabbix
Ставим дополнительную сборочную зависимость, которую разработчики забыли прописать в файлы для сборки пакетов и пакет devscripts, из которого нам пригодится утилита dch:
# apt-get install unixodbc-dev
Переходим в каталоги zabbix-2.2.0, скачиваем и накладываем заплатку:
# cd zabbix-2.2.0
# wget http://stupin.su/files/zabbix220_snmp_timeout_retries.diff
# patch -Np0 < zabbix220_snmp_timeout_retries.diff
Текст самой заплатки:
diff -Naur src/zabbix_proxy/proxy.c new/zabbix_proxy/proxy.c
--- src/zabbix_proxy/proxy.c    2013-11-12 13:16:44.000000000 +0600
+++ new/zabbix_proxy/proxy.c    2013-12-07 13:29:39.283703310 +0600
@@ -42,6 +42,7 @@
 #include "housekeeper/housekeeper.h"
 #include "../zabbix_server/pinger/pinger.h"
 #include "../zabbix_server/poller/poller.h"
+#include "../zabbix_server/poller/checks_snmp.h"
 #include "../zabbix_server/poller/checks_ipmi.h"
 #include "../zabbix_server/trapper/trapper.h"
 #include "../zabbix_server/snmptrapper/snmptrapper.h"
@@ -407,6 +408,10 @@
 #endif
                {"Timeout",                     &CONFIG_TIMEOUT,                        TYPE_INT,
                        PARM_OPT,       1,                      30},
+               {"SNMPTimeout",                 &CONFIG_SNMP_TIMEOUT,                   TYPE_INT,
+                       PARM_OPT,       1,                      30},
+               {"SNMPRetries",                 &CONFIG_SNMP_RETRIES,                   TYPE_INT,
+                       PARM_OPT,       1,                      10},
                {"TrapperTimeout",              &CONFIG_TRAPPER_TIMEOUT,                TYPE_INT,
                        PARM_OPT,       1,                      300},
                {"UnreachablePeriod",           &CONFIG_UNREACHABLE_PERIOD,             TYPE_INT,
diff -Naur src/zabbix_server/poller/checks_snmp.c new/zabbix_server/poller/checks_snmp.c
--- src/zabbix_server/poller/checks_snmp.c      2013-11-12 13:16:49.000000000 +0600
+++ new/zabbix_server/poller/checks_snmp.c      2013-12-07 12:50:31.062073112 +0600
@@ -33,6 +33,9 @@
 }
 zbx_snmp_index_t;
 
+int CONFIG_SNMP_TIMEOUT;
+int CONFIG_SNMP_RETRIES;
+
 static zbx_snmp_index_t        *snmpidx = NULL;
 static int             snmpidx_count = 0, snmpidx_alloc = 16;
 
@@ -268,10 +271,10 @@
                        break;
        }
 
-       session.retries = 0;                            /* number of retries after failed attempt */
-                                                       /* (net-snmp default = 5) */
-       session.timeout = CONFIG_TIMEOUT * 1000 * 1000; /* timeout of one attempt in microseconds */
-                                                       /* (net-snmp default = 1 second) */
+       session.retries = CONFIG_SNMP_RETRIES - 1;              /* number of retries after failed attempt */
+                                                               /* (net-snmp default = 5) */
+       session.timeout = CONFIG_SNMP_TIMEOUT * 1000 * 1000;    /* timeout of one attempt in microseconds */
+                                                               /* (net-snmp default = 1 second) */
 
 #ifdef HAVE_IPV6
        if (SUCCEED != get_address_family(item->interface.addr, &family, err, MAX_STRING_LEN))
diff -Naur src/zabbix_server/poller/checks_snmp.h new/zabbix_server/poller/checks_snmp.h
--- src/zabbix_server/poller/checks_snmp.h      2013-11-12 13:16:48.000000000 +0600
+++ new/zabbix_server/poller/checks_snmp.h      2013-12-07 12:50:31.062073112 +0600
@@ -26,7 +26,8 @@
 #include "sysinfo.h"
 
 extern char    *CONFIG_SOURCE_IP;
-extern int     CONFIG_TIMEOUT;
+extern int     CONFIG_SNMP_TIMEOUT;
+extern int     CONFIG_SNMP_RETRIES;
 
 int    get_value_snmp(DC_ITEM *item, AGENT_RESULT *value);
 
diff -Naur src/zabbix_server/server.c new/zabbix_server/server.c
--- src/zabbix_server/server.c  2013-11-12 13:17:04.000000000 +0600
+++ new/zabbix_server/server.c  2013-12-07 13:29:22.605760996 +0600
@@ -43,6 +43,7 @@
 #include "housekeeper/housekeeper.h"
 #include "pinger/pinger.h"
 #include "poller/poller.h"
+#include "poller/checks_snmp.h"
 #include "poller/checks_ipmi.h"
 #include "timer/timer.h"
 #include "trapper/trapper.h"
@@ -360,6 +361,10 @@
 #endif
                {"Timeout",                     &CONFIG_TIMEOUT,                        TYPE_INT,
                        PARM_OPT,       1,                      30},
+               {"SNMPTimeout",                 &CONFIG_SNMP_TIMEOUT,                   TYPE_INT,
+                       PARM_OPT,       1,                      30},
+               {"SNMPRetries",                 &CONFIG_SNMP_RETRIES,                   TYPE_INT,
+                       PARM_OPT,       1,                      10},
                {"TrapperTimeout",              &CONFIG_TRAPPER_TIMEOUT,                TYPE_INT,
                        PARM_OPT,       1,                      300},
                {"UnreachablePeriod",           &CONFIG_UNREACHABLE_PERIOD,             TYPE_INT,
Если всё прошло успешно, то можно внести комментарии к изменениям. Запускаем команду для редактирования журнала изменений:
# dch -i
И приводим последнюю запись к подобному виду:
zabbix (1:2.2.0-2+stupin) UNRELEASED; urgency=low

  * Non-maintainer upload.
  * Added parameters SNMPTimeout and SNMPRetries to config of Zabbix-server
    and Zabbix-proxy

 -- Vladimir Stupin <wheel69@yandex.ru>  Sat, 07 Dec 2013 13:08:13 +0600
Теперь можно собрать deb-пакеты:
# dpkg-buildpackage -us -uc -b -rfakeroot
В каталоге выше появятся собранные пакеты, которые можно установить при помощи dpkg.

2. Установка Zabbix

Установим необходимые для работы Zabbix пакеты:
# apt-get install lighttpd php5-cgi snmp-mibs-downloader mysql-server mysql-client
После этого можно установить сам сервер Zabbix, веб-интерфейс, агент и утилиты. Если вы собирали пакеты самостоятельно, можете воспользоваться dpkg. Можно также воспользоваться моим репозиторием, в который я выложил собранные пакеты. Для этого нужно вписать в файл /etc/apt/sources.list строку:
deb http://deb.stupin.su/ wheezy main
И обновить список пакетов:
# apt-get update
Ставим Zabbix из пакетов:
# dpkg -i zabbix-server-mysql_2.2.0-2+stupin_amd64.deb zabbix-agent_2.2.0-2+stupin_amd64.deb zabbix-frontend-php_2.2.0-2+stupin_all.deb zabbix-get_2.2.0-2+stupin_amd64.deb zabbix-sender_2.2.0-2+stupin_amd64.deb
Или из репозитория:
# apt-get install zabbix-server-mysql zabbix-agent zabbix-frontend-php zabbix-get zabbix-sender
Zabbix-агент позволит наблюдать за самим компьютером, на котором мы устанавливаем Zabbix.

Утилита zabbix-get может пригодиться для отладки пользовательских параметров Zabbix-агента: скриптов, которые вызываются Zabbix-агентом при запросе этого параметра. Скрипт должен выводить результат на стандартный вывод, а Zabbix-агент вернёт выведенное значение Zabbix-серверу или утилите zabbix-get.

Утилита zabbix-sender может пригодиться для скриптов, запускаемых из планировщика задач. Эти скрипты могут отправлять результаты измерений на Zabbix-сервер при помощи утилиты zabbix-sender.

3. Настройка базы данных

В процессе установки Zabbix-сервера будет выведено предложение настроить базу данных. Если вы ставите Zabbix в первый раз, то можно согласиться с предложением и настроить новую базу. Если же у вас имеется база данных, стоит ответить на предложение отрицательно и настроить подключение к базе данных вручную, в файле /etc/zabbix/zabbix_server.conf. Диалог настройки выглядит следующим образом:



Можно те же самые действия проделать и вручную. Подключаемся консольным клиентом к СУБД и создаём базу данных для Zabbix:
$ mysql -uroot -p
> CREATE DATABASE zabbix DEFAULT CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI;
Создаём пользователя, от имени которого Zabbix будет подключаться к MySQL:
> INSERT INTO user(host, user, password) VALUES('localhost', 'zabbix', PASSWORD('zabbix_password'));
> FLUSH PRIVILEGES;
Даём пользователю zabbix полный доступ к базе данных zabbix:
> GRANT ALL ON zabbix.* TO 'zabbix'@'localhost';
> FLUSH PRIVILEGES;

Подключаемся к свежесозданной БД, создаём структуру БД и наполняем её данными:
> USE zabbix;
> SOURCE /usr/share/zabbix-server-mysq/schema.sql
> SOURCE /usr/share/zabbix-server-mysq/data.sql
> SOURCE /usr/share/zabbix-server-mysq/images.sql
Задаём пароль администратора системы мониторинга и выходим из MySQL:
> UPDATE users SET passwd=MD5('zabbix_admin_password') WHERE alias='admin';
> QUIT
4. Настройка SNMP

Удобно при опросе оборудования по SNMP в качестве OID'ов указывать не их числовое значение, а текстовое представление, взятое из соответствующего MIB-файла. Никто не запрещает использовать и другие MIB-файлы, но обычно мне бывает достаточно одного лишь IF-MIB'а. Файл IF-MIB скачивается в процессе установки пакета snmp-mibs-downloader, осталось лишь прописать его использование в файл конфигурации /etc/snmp/snmp.conf, вписав в него следующую строчку:
mibs +IF-MIB
5. Настройка сервера Zabbix

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

Отредактируем файл конфигурации /etc/zabbix/zabbix_server.conf, установив значения соответствующих опций. В случае если Zabbix-сервер и сервер MySQL находятся на одном компьютере, желательно использовать Unix-сокет:
DBHost=localhost
DBSocket=/var/run/mysqld/mysqld.sock
DBName=zabbix
DBUser=zabbix
DBPassword=zabbix_password

Другие полезные настройки, на которые следует обратить внимание:
ListenIP=127.0.0.1 

StartPollers=5
StartPollersUnreachable=1
StartPingers=1
Timeout=3

SNMPTimeout=1
SNMPRetries=3
Назначение настроек:
  • ListenIP указывает IP-адрес, на котором Zabbix-сервер будет принимать подключения от веб-интерфейса и утилиты zabbix-sender. Может оказаться проще оставить эту настройку закомментированной, ограничив допустимые адреса клиентов при помощи пакетного фильтра.
  • StartPollers задаёт количество процессов, занимающихся SNMP-проверками, проверками при помощи скриптов внешнего опроса и т.п. Если в меню веб-интерфейса "Администрирование" - "Очередь" не наблюдается отставания в опросе, то всё можно оставить как есть.
  • StartPollersUnreachable задаёт количество процессов, занимающихся опросом не отвечающего оборудования. Обычно стоит сделать запас на случай крупных аварий, когда неотвечающего оборудования становится очень много.
  • StartPingers - количество процессов, занимающихся опросом по ICMP. Для опроса используется утилита fping, для которой во временном каталоге формируются списки IP-адресов. Если опрашиваемых по ICMP узлов очень много, стоит увеличить эту настройку. Возможно также поможет использование в качестве временного каталога раздела tmpfs, т.к. при этом временные файлы будут создаваться в памяти. А может быть это и не поможет, потому что дисковый кэш на практике может нивелировать накладные расходы по записи и чтению этих файлов.
  • Timeout - тот самый период ожидания ответа, значение которого в моём случае не подходило для опроса по SNMP. Для того, чтобы скрипты внешнего опроса успевали отрабатывать, я обычно задаю значение 30 секунд.
  • SNMPTimeout и SNMPRetries - настройки, добавленные путём правки исходных текстов Zabbix. Задают период ожидания ответа по SNMP и количество попыток. Поскольку протокол SNMP работает поверх протокола UDP, который не гарантирует доставку, всё-же стоит делать несколько попыток опроса, а не прекращать опрос сразу после первой же неудачи. Я задал 1 секунду и 3 попытки соответственно. Считаю это оптимальными настройками.

По мере роста количества наблюдаемых узлов и элементов данных может потребоваться увеличивать размеры кэшей и выставить в /etc/sysctl.conf настройку, ограничивающую максимальной размер одного сегмента разделяемой памяти, увеличивая её по мере необходимости:
kernel.shmmax = 268435456
Изменить эту настройку в процессе работы системы можно запустив команду вида:
# sysctl kernel.shmmax=268435456
6. Настройка веб-сервера

Настроим Lighttpd для обслуживания веб-интерфейса Zabbix. Для этого создадим файл /etc/lighttpd/conf-available/20-zabbix.conf со следующим содержимым:
alias.url += (
  "/zabbix/" => "/usr/share/zabbix/"
)

$HTTP["url"] =~ "^/zabbix/conf/" {
  url.access-deny = ("")
}

$HTTP["url"] =~ "^/zabbix/api/" {
  url.access-deny = ("")
}

$HTTP["url"] =~ "^/zabbix/include/" {
  url.access-deny = ("")
}

$HTTP["url"] =~ "^/zabbix/include/classes/" {
  url.access-deny = ("")
}
Если хочется, чтобы веб-интерфейс Zabbix был доступен по адресу с собственным доменным именем, этот файл может выглядеть следующим образом:
$HTTP["host"] == "zabbix.domain.tld" {

  alias.url += (
    "/" => "/usr/share/zabbix/"
  )

  $HTTP["url"] =~ "^/conf/" {
    url.access-deny = ("")
  }

  $HTTP["url"] =~ "^/api/" {
    url.access-deny = ("")
  }

  $HTTP["url"] =~ "^/include/" {
    url.access-deny = ("")
  }

  $HTTP["url"] =~ "^/include/classes/" {
    url.access-deny = ("")
  }
}
Затем откорректируем настройки PHP, так чтобы он удовлетворял требованиям веб-интерфейса Zabbix. Для этого создадим файл /etc/php5/mods-available/zabbix.ini и пропишем в него следующие настройки:
max_execution_time=300
memory_limit=128M
post_max_size=16M
upload_max_filesize=2M
max_input_time=300
date.timezone=Asia/Yekaterinburg
Включим использование этого файла при обработке CGI-сценариев PHP:
# cd /etc/php5/cgi/
# ln -s ../mods-available/zabbix.ini conf.d/30-zabbix.ini
Осталось включить поддержку PHP в режиме FastCGI в веб-сервере, а также включить веб-интерфейс Zabbix и попросить веб-сервер применить новые настройки:
# lighty-enable-mod fastcgi-php
# lighty-enable-mod zabbix
# /etc/init.d/lighttpd force-reload
7. Настройка веб-интерфейса

Зайдём в веб-интерфейс Zabbix и проследуем по страницам мастера настроки веб-интерфейса:

Вместо использования мастера можно просто создать вручную файл conf/zabbix.conf.php со следующим содержанием:
<?php
// Zabbix GUI configuration file
global $DB;

$DB['TYPE']     = 'MYSQL';
$DB['SERVER']   = 'localhost';
$DB['PORT']     = '0';
$DB['DATABASE'] = 'zabbix';
$DB['USER']     = 'zabbix';
$DB['PASSWORD'] = 'zabbix_db_password';

// SCHEMA is relevant only for IBM_DB2 database
$DB['SCHEMA'] = '';

$ZBX_SERVER      = 'localhost';
$ZBX_SERVER_PORT = '10051';
$ZBX_SERVER_NAME = '';

$IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
?>
8. Настройка клиента точного времени

Настраивать точное время полезно всегда. В случае системы мониторинга это особенно полезно, поскольку происходящие в системе события и исторические данные имеют привязку ко времени. Для синхронизации системного времени я использую демон OpenNTPd, разработанный в рамках проекта OpenBSD. Он не перегружен функционалом и легко настраивается. Установим пакет openntpd:
# apt-get install openntpd
И пропишем в файл конфигурации /etc/openntpd/ntpd.conf опцию servers, которая использует в качестве серверов все IP-адреса, возвращаемые по доменному имени:
servers ntp.ufanet.ru
9. Настройка почтового релея

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

Заключение

Здесь рассмотрена лишь установка системы мониторинга. Её настройка - отдельная большая тема. Чтобы получить начальное представление об устройстве системы, рекомендую обратиться к статье: FreeBSD: Установка и начальная настройка системы мониторинга Zabbix. Более подробное описание можно найти на официальной вики-странице проекта: Руководство по Zabbix 2.2.

Дополнение от 11 июня 2014 года.

В патче были две грубейшие ошибки в настройках таймаутов и количества повторов SNMP, которые долгое время оставались незамеченными. Исправил патч.