Детективная история про 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.

    Ура. Хэппи-энд.
    • +17
    • 2,4k
    • 5
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 5
    • +2
      Технически, пересборка является решением, а юридически — есть мнение, что это недопустимо.

      Ключ --without-openssl добавлен по лицензионным соображениям, так как существенные части библиотеки лицензированы под GPLv2, которая требует, чтобы при распространении весь код был под GPLv2. Итог: по мнению дебианщиков, результат сборки с OpenSSL запрещено распространять в бинарном виде. RedHat пыталась обойти эту юридическую проблему (GPL + OpenSSL) путем объявления OpenSSL частью операционной системы.
      • 0
        Спасибо, ценное замечание.
        В моем случае разработка была для внутреннего использования, и проблема с лицензией не стояла.

        Интересно, но при этом ipmitool, поставленная из тех же реп в бинарном виде работает корректно.
        • 0
          Ipmitool распространяется не под GPL, а под BSD 3-clause license, поэтому проблема совместимости с лицензией OpenSSL не стоит.
      • +2

        Об этом много написано, только в документации сторонних продуктов, например в zabbix: https://www.zabbix.com/documentation/3.4/ru/manual/installation/known_issues


        Форум заббикса по ключевым словам выдает много тем с одним и тем же решением, так что не вы первый и не вы последний :)

        • 0
          В документации на Zabbix написано, а в документации на саму библиотеку не написано, забавно.
          Хорошая ссылка, кстати, прекрасно показывающая, какие неочевидности и баги могут быть в сторонних библиотеках.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое