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

в 17:00, , рубрики: C, IPMI, linux, openipmi, rmcp, wireshark, Программирование, Сетевые технологии

Внутри будет немного кода на Си, немного дампов 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:
https://launchpad.net/~vvk/+archive/ubuntu/openipmi
где добрый человек пересобирает эту либу для Ubuntu с поддержкой OpenSSL.

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

Автор: Whuthering

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js