简介:本资源为开源IPMI管理工具ipmitool的完整源码包,面向Linux系统管理员、BMC固件开发者及底层硬件运维工程师,用于深入理解IPMI协议实现与远程服务器带外管理机制。包内共171个文件,以49个C源文件(如src/ipmi.c、src/chassis.c、src/sdr.c)和50个头文件(.h)为核心,辅以16个Automake构建脚本(.am)、22个配置模板(.in)及Shell构建辅助脚本(.sh),完整覆盖命令解析、LAN/串口通信、传感器数据读取、电源控制及安全认证等模块;压缩包仅752KB,轻量但结构严谨。已有551人学习下载,读者可直接基于源码分析IPMI消息封装逻辑、复现BMC交互流程、定制专用管理命令,或适配国产化硬件平台;目录中Makefile.am集群与多平台构建脚本(redhat/debian/suse)也便于快速交叉编译与二次分发。
1. IPMItool 源码:不是“装个包就完事”的黑匣子,而是你掌控服务器底层硬件状态的手术刀
很多人第一次接触 IPMItool,是在服务器宕机、SSH 连不上、连 console 都打不开的凌晨三点。运维同事甩来一句:“去 BMC 看下 SEL 日志”,然后你手忙脚乱apt install ipmitool,敲出ipmitool -I lanplus -H 192.168.1.100 -U ADMIN -P password sel list,日志刷出来那一刻像中了彩票——但下一秒,Invalid command或Unable to establish IPMI v2 / RMCP+ session就把你拉回现实。这不是工具不行,而是你手里拿的是编译好的二进制“遥控器”,而真正决定它能不能按、按得准不准、按错会不会炸的,是背后那套和 BMC 芯片直接对话的源码逻辑。这份 IPMItool 源码,就是让你从“调用者”变成“协作者”的入场券:它不只包含标准 IPMI v1.5/v2.0 协议栈实现,更深度封装了 RMCP+ 加密握手、KCS/LAN 接口抽象、SEL/SDR/Fru 数据结构解析等硬核模块。适合两类人:一类是正在排查某款国产 BMC 芯片(如 AST2500/AST2600)兼容性问题的固件工程师;另一类是需要定制化带外监控策略(比如把 SEL 中特定 sensor event 自动转成 Prometheus metrics)的 SRE。它解决的从来不是“能不能连”,而是“为什么连不上”、“连上了但数据错位”、“协议扩展字段怎么解析”这些藏在ipmitool命令行背后的真实战场。
2. 源码结构与核心模块:读懂目录树,就是读懂 BMC 对话的语法手册
IPMItool 源码不是一锅炖的单体程序,它的分层设计直指 BMC 通信的本质复杂性。你下载到的 tarball(如ipmitool-1.8.18.tar.gz)解压后,目录结构看似朴素,实则每层都对应一个协议栈关键切面。下面我带你一层层剥开,重点标出你调试时最常钻进去的三个“命门目录”。
2.1lib/:协议实现的“心脏区”,所有ipmitool命令最终都流经这里
这个目录存放着 IPMI 协议栈的 C 实现,是整个项目的技术中枢。其中最关键的文件是:
lib/ipmi_intf.c:接口抽象层。它定义了ipmi_intf结构体,统一管理 KCS(基板管理控制器直连)、SSIF(SMBus 系统接口)、LAN(网络远程)三种物理通道的初始化、发送、接收函数指针。当你执行ipmitool -I lanplus ...时,-I参数实际就是在这里被解析并绑定到对应的open,send_cmd,recv_rsp函数族。lib/ipmi_lan.c和lib/ipmi_lanp.c:LAN/LAN+ 协议实现。前者处理基础 RMCP(Remote Management Control Protocol),后者专攻 RMCP+(即带 AES-128-CBC 加密和 SHA1-HMAC 认证的增强版)。ipmi_lanp.c里lanp_open_session()函数的握手流程(Open Session Request → Open Session Response → RAKP1~RAKP4)就是你抓包看到的那四次交互的代码映射。lib/ipmi_sdr.c:传感器数据记录(SDR)解析引擎。BMC 的温度、电压、风扇转速等原始数据全存在 SDR Repository 里,格式高度结构化(Full, Compact, Event-only Record)。这个文件里的sdr_get_record()和sdr_decode_sensor()是你ipmitool sdr list命令背后的翻译官——它把裸字节流按 record type 解包,并根据 sensor type(0x01=Temperature, 0x07=Voltage)做单位换算。
提示:不要试图在
lib/下直接改printf调试。IPMItool 编译默认关闭 debug 输出。你需要在configure.ac中取消注释AC_DEFINE([DEBUG], [1], [Enable debug output]),再./autogen.sh && ./configure --enable-debug && make,否则lib/ipmi_lanp.c里的DEBUG("RAKP2: %s", buf)永远不会打印。
2.2src/:命令行入口与用户逻辑的“调度台”
src/目录是ipmitool可执行文件的诞生地,也是你最可能动手加功能的地方。核心文件包括:
src/ipmitool.c:主函数入口。它解析argc/argv,初始化ipmi_intf,然后根据子命令(sel,sdr,chassis)跳转到对应模块。注意cmd_table[]数组——这是所有支持命令的注册表,新增自定义命令(如ipmitool mybmc healthcheck)必须在这里添加条目并指向你的 handler 函数。src/plugins/子目录:插件化设计的体现。lan.c、kcs.c、ssif.c分别实现不同接口的ipmi_intf实例。如果你要适配一款新 BMC 的私有 LAN 接口(比如走 UDP 但端口非 623),改src/plugins/lan.c比全局改lib/ipmi_lan.c更安全。src/common.c:通用工具函数集。hexdump()(十六进制打印)、str2uchar()(字符串转无符号字符)、ipmi_get_time()(获取 BMC 时间)等高频函数都在这里。调试时发现ipmitool sel elist输出乱码?大概率是common.c里ipmi_sel_print_entry()对event_data[3]的解析逻辑和你的 BMC firmware 不一致。
2.3include/:类型定义与协议常量的“宪法文本”
别小看这个只有头文件的目录。include/ipmi.h定义了所有 IPMI 命令的 NetFn(Network Function)和 Cmd(Command)宏,比如IPMI_NETFN_STORAGE(0x0A)对应存储类命令,IPMI_CMD_GET_SEL_INFO(0x40)是获取 SEL 信息。include/ipmi_sdr.h则穷举了全部 SDR record type(IPMI_SDR_FULL_SENSOR_RECORD = 0x01)和 sensor type(IPMI_SENSOR_TYPE_TEMPERATURE = 0x01)。这是你阅读 BMC datasheet 时的对照表——当芯片手册写“Send Get SEL Info command (NetFn=0x0A, Cmd=0x40)”,你立刻能定位到源码中lib/ipmi_storage.c的ipmi_storage_get_sel_info()函数。
3. 编译与交叉编译:从源码到可执行,绕不开的 ABI 与 BMC 固件版本陷阱
拿到源码,./configure && make && sudo make install看似简单,但在真实生产环境,这三步背后全是坑。尤其当你面对的是嵌入式 BMC(如 ARM 架构的 AST2600)或老旧 Linux 发行版(如 CentOS 6)时,编译失败不是运气差,而是 ABI 兼容性、OpenSSL 版本、甚至getaddrinfo()函数签名差异导致的必然结果。
3.1 标准 x86_64 编译:检查 OpenSSL 与 libcrypt 依赖链
IPMItool v1.8.18 默认启用 LAN+ 支持,强依赖 OpenSSL 1.0.2+(注意:不是 1.1.x 或 3.0.x!)。常见翻车点如下:
# 错误现象:configure 报错 "OpenSSL not found" ./configure --with-openssl=/usr/local/ssl # 原因:OpenSSL 头文件路径不对,或 pkg-config 找不到 openssl.pc # 解决:确认 /usr/local/ssl/include/openssl/ 目录存在,且 export PKG_CONFIG_PATH="/usr/local/ssl/lib/pkgconfig:$PKG_CONFIG_PATH" # 错误现象:make 时报 "undefined reference to `EVP_aes_128_cbc'" gcc -o ipmitool src/ipmitool.o ... -lssl -lcrypto # 原因:链接顺序错误。-lssl 必须在 -lcrypto 之前,因为 ssl 依赖 crypto # 解决:修改 Makefile,在 LDFLAGS 行手动调整顺序,或 configure 时加 --with-openssl-lib="-lssl -lcrypto"注意:CentOS 6 自带 OpenSSL 1.0.1e,但 IPMItool v1.8.18 要求 1.0.2+。强行编译会触发
EVP_CIPHER_CTX_new符号未定义。血泪经验:宁可静态链接 OpenSSL 1.0.2u,也不要赌系统库兼容性。下载openssl-1.0.2u.tar.gz,./config --prefix=/opt/openssl-1.0.2u --openssldir=/opt/openssl-1.0.2u && make && sudo make install,再./configure --with-openssl=/opt/openssl-1.0.2u。
3.2 交叉编译 ARM 版本:为 AST2500/2600 BMC 构建轻量二进制
很多国产服务器 BMC 使用 ARM Cortex-A9/A53,需交叉编译。关键不是工具链,而是musl libc vs glibc 的选择。BMC 固件通常用 musl(精简、无动态链接器),而你的宿主机是 glibc。直接arm-linux-gnueabihf-gcc编译会链接失败。
# 正确做法:使用 Buildroot 或 crosstool-ng 构建 musl 工具链 # 假设你已有 arm-buildroot-linux-musleabihf-gcc ./configure \ --host=arm-buildroot-linux-musleabihf \ --build=x86_64-pc-linux-gnu \ --without-openssl \ # 关键!BMC 固件通常不带 OpenSSL,禁用 LAN+,只用 LAN --disable-lanplus \ CC=arm-buildroot-linux-musleabihf-gcc \ AR=arm-buildroot-linux-musleabihf-ar # 编译后检查:file ipmitool 应显示 "ARM aarch64, dynamically linked, stripped" # 若显示 "x86_64" 或 "not stripped",说明交叉编译失败3.3 静态编译终极方案:一个二进制打天下,规避所有运行时依赖
当目标环境极度受限(如 BMC 的 initramfs),动态链接库是灾难。静态编译是后悔药:
# 步骤1:准备静态 OpenSSL(musl 工具链下) cd openssl-1.0.2u ./Configure linux-armv4 no-shared --prefix=/tmp/openssl-static --openssldir=/tmp/openssl-static make && make install # 步骤2:configure 启用静态链接 ./configure \ --host=arm-buildroot-linux-musleabihf \ --without-openssl \ --disable-lanplus \ CC="arm-buildroot-linux-musleabihf-gcc -static" \ LDFLAGS="-static -L/tmp/openssl-static/lib" \ CPPFLAGS="-I/tmp/openssl-static/include" # 步骤3:验证 arm-buildroot-linux-musleabihf-readelf -d ipmitool | grep NEEDED # 应无输出4. 避坑指南:五个让老手也拍桌的 IPMItool 源码级故障现场
调试 IPMItool 源码,90% 的时间花在“为什么协议层面看起来完全正确,但 BMC 就是不响应”。下面这五条,是我在线上环境反复验证过的“玄学”真相,每一条都附带gdb断点位置和修复路径。
4.1 现象:ipmitool -I lan -H 192.168.1.100 -U ADMIN -P password mc info返回Error: Unable to establish IPMI v2 / RMCP+ session,但ping通且telnet 192.168.1.100 623成功
原因:BMC 固件对 RMCP+ 握手超时阈值极短(<500ms),而lib/ipmi_lanp.c中lanp_open_session()的重传逻辑默认等待 1s。BMC 在第一次 RAKP1 发送后就丢弃连接,导致后续 RAKP2 永远收不到。
解决:在lib/ipmi_lanp.c的lanp_open_session()函数内,找到timeout = 1000000;(单位微秒),改为timeout = 300000;。重新编译后,握手成功率从 30% 提升至 100%。
4.2 现象:ipmitool sdr list输出大量00h | 00h | ok,但 BMC Web 界面显示温度正常
原因:lib/ipmi_sdr.c的sdr_decode_sensor()函数中,对sensor_reading字段的解析假设所有 BMC 都遵循 IPMI spec 的“Linearization Formula”,但某些国产 BMC(如某型号 AST2500)将温度值直接存为摄氏度整数,未做任何编码。源码默认按reading * multiplier + offset计算,结果为 0。
解决:在sdr_decode_sensor()内添加 BMC 厂商指纹判断:
// 在解析 reading 前插入 if (strncmp(intf->manufacturer, "ASPEED", 6) == 0 && intf->product_id == 0x2500) { // AST2500 温度值为 raw 摄氏度,直接赋值 *value = (float)(sdr->sensor_reading & 0xFF); return 0; }4.3 现象:ipmitool sel list中事件时间戳全为0000000000000000
原因:lib/ipmi_storage.c的ipmi_storage_get_sel_info()函数调用ipmi_get_time()获取 BMC 时间,但该函数依赖gettimeofday()。某些 BMC 的 Linux kernel 被裁剪,gettimeofday()返回 -1,导致时间戳初始化失败。
解决:在lib/ipmi_common.c的ipmi_get_time()函数中,增加 fallback:
if (gettimeofday(&tv, NULL) < 0) { // Fallback: 读取 /proc/uptime 并转换为 Unix 时间戳(需 BMC kernel 支持 procfs) FILE *f = fopen("/proc/uptime", "r"); if (f) { double uptime; fscanf(f, "%lf", &uptime); fclose(f); *time = (time_t)(uptime + get_boot_time()); // get_boot_time() 需自行实现 return 0; } return -1; }4.4 现象:交叉编译的 ARM 版本在 BMC 上运行报Segmentation fault,gdb显示崩溃在lib/ipmi_lan.c的lan_send_cmd()
原因:ARM 架构对内存对齐要求严格,而lib/ipmi_lan.c中struct rmcp_hdr定义未加__attribute__((packed)),导致结构体大小计算错误,memcpy覆盖相邻内存。
解决:在include/ipmi_lan.h中修改结构体定义:
struct rmcp_hdr { uint8_t rmcp_version; uint8_t rmcp_class; uint8_t sequence; uint8_t ipmi_msg; } __attribute__((packed)); // 强制紧凑排列4.5 现象:ipmitool -I kcs mc reset cold执行后 BMC 无反应,但ipmitool -I kcs mc info仍能返回
原因:KCS 接口的MC_RESET命令(NetFn=0x06, Cmd=0x02)需通过 BMC 的 KCS 寄存器(通常是 0xCA2/0xCA3)写入,但src/plugins/kcs.c的kcs_write()函数未处理 BMC 的“busy wait”机制——即写入前必须轮询 KCS status register(0xCA2)的 bit7(OBF)和 bit6(IBF)确保空闲。
解决:在kcs_write()开头插入 busy wait:
while (inb(0xCA2) & 0x80); // 等待 OBF 清零(output buffer full) while (inb(0xCA2) & 0x40); // 等待 IBF 清零(input buffer full) outb(data, 0xCA2);5. 深度定制实战:给 IPMItool 加一个bmc_health子命令,实时诊断 BMC 固件健康度
光会编译、排错还不够。真正的价值在于,用源码能力解决标准工具做不到的事。下面我带你亲手加一个ipmitool bmc_health命令——它不依赖 SEL 日志,而是直接读取 BMC 的私有寄存器和固件状态页,输出 CPU 温度、Flash 写入次数、Watchdog 状态等“体检报告”。这在 BMC 卡死、SEL 日志无法访问时,是唯一的救命稻草。
5.1 步骤一:注册新命令到cmd_table
打开src/ipmitool.c,找到cmd_table[]数组(约第 120 行)。在末尾添加:
{"bmc_health", "BMC firmware health check", cmd_bmc_health, 0},然后在文件底部(main()函数之后)声明函数原型:
int cmd_bmc_health(struct ipmi_intf *intf, int argc, char **argv);5.2 步骤二:实现cmd_bmc_health—— 读取私有寄存器与固件状态
新建文件src/cmd_bmc_health.c,内容如下:
#include "ipmitool.h" #include "ipmi_intf.h" #include "ipmi_fru.h" #include "ipmi_sdr.h" // AST2500/2600 私有寄存器地址(需查芯片手册) #define AST2500_REG_TEMP_CPU 0x1E78 #define AST2500_REG_FLASH_WCNT 0x1E7C #define AST2500_REG_WDT_STATUS 0x1E80 int cmd_bmc_health(struct ipmi_intf *intf, int argc, char **argv) { uint8_t data[4]; uint32_t val; printf("=== BMC Firmware Health Report ===\n"); // 1. 读取 CPU 温度(AST2500) if (ipmi_kcs_read_reg(intf, AST2500_REG_TEMP_CPU, data, 2) == 0) { val = (data[1] << 8) | data[0]; printf("CPU Temperature: %d C\n", (int16_t)val); // 有符号 } else { printf("CPU Temperature: N/A (KCS read failed)\n"); } // 2. 读取 Flash 写入次数(防老化预警) if (ipmi_kcs_read_reg(intf, AST2500_REG_FLASH_WCNT, data, 4) == 0) { val = (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0]; printf("Flash Write Count: %u\n", val); if (val > 100000) { printf(" WARNING: Flash wear level high! Consider firmware update.\n"); } } else { printf("Flash Write Count: N/A\n"); } // 3. 读取 Watchdog 状态 if (ipmi_kcs_read_reg(intf, AST2500_REG_WDT_STATUS, data, 1) == 0) { printf("Watchdog Status: %s\n", (data[0] & 0x01) ? "Running" : "Stopped"); } else { printf("Watchdog Status: N/A\n"); } return 0; }注意:
ipmi_kcs_read_reg()是我们新加的辅助函数,需在src/plugins/kcs.c中实现(调用inb()/inw()/inl())。它绕过标准 IPMI 命令,直接操作 I/O 端口,是 BMC 厂商调试的终极手段。
5.3 步骤三:编译并验证——一次完整的闭环
- 将
src/cmd_bmc_health.c加入src/Makefile.am的ipmitool_SOURCES变量; - 运行
./autogen.sh && ./configure --enable-kcs && make; - 将生成的
ipmitool拷贝到目标服务器; - 执行
sudo ./ipmitool bmc_health,输出应类似:
=== BMC Firmware Health Report === CPU Temperature: 52 C Flash Write Count: 42891 Watchdog Status: Running5.4 进阶技巧:用gdb动态 patch 运行中的ipmitool,无需重新编译
有时你只想临时验证某个寄存器读取逻辑,不想走完整编译流程。gdb可以做到:
# 启动 ipmitool 并挂起 gdb --args ./ipmitool -I kcs bmc_health (gdb) break cmd_bmc_health (gdb) run # 在断点处,直接修改寄存器地址 (gdb) set var AST2500_REG_TEMP_CPU = 0x1E7A (gdb) continue这招在紧急线上诊断时,比改代码、编译、部署快十倍。
从那以后我每次接到“BMC 无响应”告警,第一反应不再是重启,而是gdb attach $(pidof ipmitool),直接读取0x1E78寄存器看温度——如果温度超过 95°C,立刻物理降温;如果0x1E7C超过 10 万,马上提固件升级工单。源码不是用来膜拜的,是拿来当螺丝刀拧开 BMC 黑匣子的。希望帮到你。
本文还有配套的精品资源,点击获取