Детективная история про RMCP+ и OpenSSL, или как Wireshark помог победить incorrect argument в OpenIPMI

Внутри будет немного кода на Си, немного дампов Wireshark’а и чуть-чуть консольных команд.
Дано: несколько железок, которые должны опрашиваться по интерфейсу IPMI из под GNU/Linux, и две из них, которые отказывались это делать.

image

IPMI (от англ. Intelligent Platform Management Interface) — интеллектуальный интерфейс управления платформой, предназначенный для автономного мониторинга и управления функциями, встроенными непосредственно в аппаратное и микропрограммное обеспечения серверных платформ. Ключевые характеристики IPMI — мониторинг, восстановление функций управления, журналирование и инвентаризация, которые доступны независимо от процессора, BIOS’a и операционной системы. Функции управления платформой могут быть доступны, даже если система находится в выключенном состоянии. (Wikipedia)

«Отказывались делать» — при попытке подключиться к ним как через стандартную утилиту ipmitool, так и через мою разработку используя библиотеку libOpenIPMI, соединение завершалось с ошибкой таймаута.

OpenIPMI is an effort to create a full-function IPMI system to allow full access to all IPMI information on a server and to abstract it to a level that will make it easy to use. See the SourceForge page for the source code.
(OpenIPMI)

Первое решение нашлось быстро:

ipmitool -I lanplus -H 192.168.14.5 -U ADMIN -P ADMIN mc info

Здесь, помимо стандартных реквизитов подключения по IPMI, явно указывается необходимость использования протокола RMCP+ (входящего в спецификацию IPMI 2.0).

Казалось бы, с библиотекой OpenIPMI тоже должно быть все просто.

Хоть с документацией у этой библиотеки всё весьма сложно: в качестве документации предлагается большая книга (да, именно книга в PDF-формате) под названием «A Gentle Introduction to IPMI«. То есть невозможно прочитать краткий HowTo или Readme, посмотреть примеры и начинать писать код, периодически поглядывая доки для справки, но хуже другое: не смотря на подробное описание архитектуры IPMI и функций библиотеки, в этом самом руководстве пропущены некоторые элементарнейшие вещи. Например, о том, как подключаться используя RMCP+.

Бегло пробежавшись по хидерам библиотеки, находим в define’ах то что нужно и заменяем в ipmi_ip_setup_con()

IPMI_AUTHTYPE_MD5 на IPMI_AUTHTYPE_RMCP_PLUS

И тут нас поджидает следущая проблема: ошибка таймаута действительно исчезла, однако функция подключения стала выдавать ошибку Incorrect argument.

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

На ум напрашиваются два пути:

  1. сделать debug-сборку библиотеки и пошагово изучать отладчиком, что, где и когда происходит
  2. сначала посмотреть на всё происходящее со стороны, наблюдая за обменом пакетами между клиентом (моим приложением) и сервером (железкой) и сравнить, в чем разница между моей реализацией и ipmitool’ом, а уже потом лезть в код.

Интуиция и желание приключений подтолкнули ко второму варианту и не ошиблись.

Запускаем wireshark, настраиваем фильтр и начинаем изучать.

image

Видно, что клиент и сервер обмениваются запросами-ответами, в случае работы с ipmitool обмен успешно продолжается дальше, а при использовании libOpenIPMI всё затыкается.

Вопрос: событие incorrect argument возникает где-то в недрах библиотеки, или же что-то не нравится самому устройству?

Сравниваем ответы от железки:

image
(Успешный ответ при использовании ipmitool)

image
(Не очень-то успешный ответ при использовании нашего приложения)

Как можно заметить, ответ от устройства действительно различается — в случае ошибки длина блока данных всего 7 байт.

Я попробовал поискать в Сети нормальное описание RMCP+ протокола, чтобы понять, что и как кодируется в этих данных, но это было безрезультатно.

Возник следущий вопрос: чем отличаются запросы, отправленные серверу, что в одном случае он отвечает нормально, а во втором случае что-то идет не так?

image

image

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

Покопавшись в исходниках функции установления соединения, в send_rmcpp_open_session
был найден алгоритм сборки пакета:

if ((int) lan->cparm.auth == IPMI_LANP_AUTHENTICATION_ALGORITHM_BMCPICK)     data[11] = 0; /* Let the BMC pick */ else {     data[11] = 8;     data[12] = lan->cparm.auth; }  data[16] = 1; /* integrity algorithm */ if ((int) lan->cparm.integ == IPMI_LANP_INTEGRITY_ALGORITHM_BMCPICK)     data[19] = 0; /* Let the BMC pick */ else {     data[19] = 8;     data[20] = lan->cparm.integ; }  data[24] = 2; /* confidentiality algorithm */ if ((int) lan->cparm.conf == IPMI_LANP_CONFIDENTIALITY_ALGORITHM_BMCPICK)     data[27] = 0; /* Let the BMC pick */ else {     data[27] = 8;     data[28] = lan->cparm.conf; } 

Это уже становилось интересно. Байты 0x08 в посылке явно бросались в глаза и говорили что «это вот оно самое».

Там же в исходниках нашлись define’ы различных вариантов аутентификации и подобного:

#define     IPMI_LANP_AUTHENTICATION_ALGORITHM_BMCPICK   (~0) #define     IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_NONE   0 #define     IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_SHA1   1 #define     IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_MD5   2 

Из этого следовал явный вывод, что ipmitool инициировало подключение с аутентификацией SHA-1, а наше приложение с libOpenIPMI почему-то пыталось подключиться вообще без защиты, получая в ответ игнор (видимо, устройству ну очень не нравились незащищенные подключения).

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

Дальнейшее изучение исходников показало, что варианты аутентификации берутся из глобального массива auths[], куда они добавляются процедурой ipmi_rmcpp_register_authentication(), а вот сама процедура… где она вызывается-то? Ищем и находим:

#ifdef HAVE_OPENSSL ipmi_rmcpp_register_authentication     (IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_MD5, NULL); ipmi_rmcpp_register_authentication     (IPMI_LANP_AUTHENTICATION_ALGORITHM_RAKP_HMAC_SHA1, NULL); #endif

вон он и ответ.

Стандартный пакет libopenipmi0 в Ubuntu и Debian собран без поддержки OpenSSL, необходимой для этих функций. И хоть кто-нибудь бы слово написал об этом нюансе в документации!

Убедиться в этом можно, выполнив

apt-get source libopenipmi0

и заглянув в файл debian/rules встретив там вполне четкую строчку —without-openssl

Решение — пересобрать пакет как надо.

sudo apt-get install devscripts build-essential fakeroot sudo apt-get build-dep libopenipmi0 apt-get source libopenipmi0 # заменяем в debian/rules: # --without-openssl  =>  --with-openssl sudo dpkg-buildpackage debuild -us -uc 

Устанавливаем, и проверяем, что все заработало как надо.

Чуть позже нашелся ppa, где добрый человек пересобирает эту либу для Ubuntu с поддержкой OpenSSL.

Ура. Хэппи-энд.

FavoriteLoadingДобавить в избранное

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *