
简介IPMItool 源码包面向服务器管理开发者与运维人员围绕 IPMI/BMC 远程监控和硬件级管理场景提供可编译、可研读的命令行工具实现。压缩包共 171 个文件约 752KB以 49 个 C 源文件、50 个头文件为核心辅以 22 个 in 模板、16 个 am 构建脚本及配置脚本便于按模块梳理源码结构与编译流程。已有 552 人学习下载。源码覆盖命令处理、IPMI 协议、LAN/串行 BMC 通信、SDR 传感器记录、chassis 电源控制与安全认证等模块读者可借此理解 ipmitool 的请求响应链路和跨平台构建方式并为新增命令、性能优化或特定硬件适配提供参考适合有 C 与 Linux 基础的技术人员深入研读。1. IPMItool 源码到底在管什么从一条 mc info 说起服务器风扇突然满速、面板红灯常亮系统侧却没有任何进程报错——这种场景里唯一能说话的是 BMC。IPMItool 源码就是一套把 IPMI BMC 从「黑匣子」变成「可观测设备」的命令行实现带内走 KCS 驱动、带外走 RMCP/RMCP 的 UDP 623把传感器读数、SEL 事件日志、FRU 资产信息、机箱电源状态一条条取出来。手里这份源码适合三类人要给自研板卡做带外管理适配的固件工程师、要批量巡检机器且不想依赖厂商私有工具的运维、以及需要把 IPMI 调用嵌进自有平台的后端开发。它不解决「BMC 固件怎么写」但解决「怎么用标准协议把 BMC 里的东西稳定取出来取不到时知道卡在哪一层」。翻源码最大的收益不是读懂每个命令而是搞清一条请求从 argv 到网线的完整路径。2. 源码地图从 main 到命令表的三层分发拿到一份 C 项目源码最怕的是从第一个文件顺序读下去。IPMItool 这类工具型项目结构其实很规整——入口只有一处分发只有两张表剩下的都是各命令自己处理参数。先把这两张表找到后面读任何一个 ipmi_xxx.c 都有参照系。2.1 目录分层src、lib、include、src/plugins 各管什么src/目录是命令层的集合一个子命令基本对应一个ipmi_xxx.cipmi_sdr.c管传感器仓库、ipmi_sel.c管事件日志、ipmi_fru.c管资产信息、ipmi_sol.c管串口重定向、ipmi_mc.c管控制器自身信息。主入口ipmitool.c也在这个目录下它不实现任何具体命令只负责解析全局参数、选出接口、把剩余 argv 交给命令函数。src/plugins/目录放的是传输层实现。ipmi_lan.c对应 RMCP 1.5ipmi_lanplus.c对应 RMCP 加密会话ipmi_open.c走 Linux 的 OpenIPMI 字符设备ipmi_bmc.c走厂商私有驱动ipmi_serial.c走串口直连ipmi_dummy.c是个空实现专门用来在没硬件时验证上层逻辑。这一层决定了「数据从哪条路出去」与命令本身完全解耦所以-I参数换接口时上层命令代码一行都不用改。lib/是跨命令复用的协议层SDR 仓库的检索与缓存、字符串与事件类型的映射表、SEL 时间戳的转换、SOL 载荷的封装都在这里。include/ipmitool/是构建时被引用的头文件目录定义了ipmi_intf、ipmi_rq、ipmi_rs这些贯穿全项目的结构体。实际读代码时建议先扫一眼头文件再回头看实现比在lib/里逐文件摸索快得多。2.2 命令分发链ipmi_main → 命令表 → ipmi_xxx_main所有命令都挂在同一张静态表上表项结构固定命令名、绑定的接口多数为 NULL 表示走默认、处理函数指针、帮助文本。主流程解析完-I/-H/-U/-P这些全局参数后拿第一个非选项参数在这张表里做一次字符串比对命中就调用对应函数把剩余 argc/argv 原样传下去。/* src/ipmitool.c 中的命令表此处做了精简只保留结构关系 */ struct ipmi_cmd { char * name; /* argv 里的子命令名 */ struct ipmi_intf * intf; /* 绑定接口NULL 表示用 -I 指定的 */ int (*func)(struct ipmi_intf * intf, int argc, char ** argv); /* 统一签名 */ char * desc; }; static struct ipmi_cmd ipmi_cmds[] { { sdr, NULL, ipmi_sdr_main, 打印传感器数据仓库 }, { sensor, NULL, ipmi_sensor_main, 打印传感器读数 }, { sel, NULL, ipmi_sel_main, 读写系统事件日志 }, { fru, NULL, ipmi_fru_main, 打印现场可替换单元信息 }, { raw, NULL, ipmi_raw_main, 直接下发原始 netfn/cmd }, { NULL, NULL, NULL, NULL } /* 哨兵遍历时靠它收尾 */ };这张表的价值在于它规定了二次开发的最小改动面新增一个命令只要写一个int ipmi_xxx_main(struct ipmi_intf *, int, char **)再往表里加一行参数解析、接口选择、错误码打印全部复用现成逻辑。intf字段留 NULL 是常态只有在某个命令必须固定走某条通路时才会填具体接口实例。注意表尾那个全 NULL 的哨兵项遍历代码依赖它退出循环新增条目必须插在它之前否则新命令永远匹配不到。2.3 接口插件层ipmi_intf 的 open/sendrecv 契约传输层能被随意替换靠的是一个函数指针结构体。每个插件文件末尾定义一个该结构体的实例把所有实现函数填进去然后在接口表里引用这个实例。上层命令只认结构体不认具体是网口还是字符设备。/* include/ipmitool/ipmi_intf.h 精简后的接口契约 */ struct ipmi_intf { char * name; /* 完整名对应 -I lanplus */ char * shortname; /* 短名对应 -I l */ char * desc; int (*open)(struct ipmi_intf * intf); /* 建链、握手、认证 */ void (*close)(struct ipmi_intf * intf); /* 释放会话 */ struct ipmi_rs * (*sendrecv)(struct ipmi_intf * intf, struct ipmi_rq * req); /* 一问一答 */ int (*sendcmd)(struct ipmi_intf * intf, struct ipmi_rq * req); /* 只发不收 */ /* 其余字段略厂商信息、目标地址、桥接参数等 */ };sendrecv是核心契约入参是请求结构netfn、cmd、data、data_len出参是响应结构完成码 ccode、数据指针、数据长度。任何插件只要正确实现这一对语义上面所有命令都能直接跑。调试时如果怀疑是协议层问题可以先用-I dummy跑一遍空接口会立刻返回能快速区分「参数解析错了」还是「网络/驱动层没通」。接口表的注册方式在不同版本里略有差异有的是静态数组直接引用各插件实例有的是注册函数在启动时挂链。定位方法一致在src/plugins/ipmi_intf.c里搜intf_table或ipmi_intf_register看到的那张表就是全部可用接口的清单加插件必须同步改这里。2.4 读源码的推荐顺序先读ipmitool.c里的main()和它调用的主流程函数弄清全局参数怎么解析、哪些参数会被透传给子命令。然后跳到ipmi_intf.c看接口表把-I的取值和插件文件对应起来。第三步挑一个最简单的命令文件ipmi_mc.c是合适的选择它只发一条 Get Device ID请求构造、响应校验、字段打印三段结构一目了然看完就能照着写自己的命令。第四步再进lib/看 SDR 相关的缓存逻辑因为sdr和sensor都依赖它先看命令层会反复被缓存代码打断。最后回头看ipmi_lanplus.c这个文件涉及会话建立和加密填充代码量大且和硬件无关放在最后读不容易劝退。3. 编译与运行从 bootstrap 到两条通路同时验通源码能读和能跑是两件事。IPMItool 的构建体系是 autotools理论上三步就能出二进制但带外接口依赖外部加密库裁剪和交叉编译时容易被默认配置坑到。这一章按「依赖 → 构建 → 验证 → 交叉编译」走一遍每一步给出可对照的检查点。3.1 依赖清单与 configure 选项构建前需要准备的东西不多但缺一样就少一个接口而 configure 默认不会因为缺接口而报错终止只是静默跳过——这是后续-I lanplus报「不支持的接口」的常见根因。依赖用途缺失后果autoconf / automake / libtool生成 configure 与 Makefilebootstrap.sh直接失败C 编译器与 make编译无法构建TLS 开发库lanplus 的 RMCP 加密会话lanplus 接口不编译只留 lanreadline 开发库交互式命令行编辑交互模式退化为无补全内核头文件open 接口访问字符设备open 接口不可用configure 选项决定编哪些接口常用的几个如下。带--enable的默认值在不同发行包里有差异显式写出来最稳。./bootstrap.sh # git 检出时必需tar 包内已含 configure 可跳过 ./configure \ --prefix/opt/ipmitool \ --enable-intf-lan \ # RMCP 1.5老 BMC 兼容用 --enable-intf-lanplus \ # RMCP现代 BMC 走这条 --enable-intf-open \ # 带内 OpenIPMI 字符设备 --enable-intf-dummy \ # 空接口无硬件自检用 --disable-intf-serial # 不用串口直连就裁掉减少依赖 make -j$(nproc) make install--enable-intf-lanplus写上去不等于编译成功configure 只有在检测到 TLS 库的头文件和链接库时才会真正启用它。判断依据是 configure 结束时的特性汇总段里面会逐个列出启用的接口名也可以在config.log里搜库名看到检测失败的具体原因。构建完成后用ipmitool -h看帮助里的接口列表列表中没有 lanplus 就是没编进去不必再往下试。3.2 构建产物检查与最小自检编译通过后不要急着连真机先在本地做两件事确认二进制能加载所需动态库确认接口表完整。前者用ldd看有没有 not found后者直接看帮助输出。ldd src/ipmitool | grep -i not found # 有输出说明运行期缺库 src/ipmitool -h | sed -n /Interfaces/,/^$/p # 列出已编译进二进制的接口 src/ipmitool -I dummy mc info # 空接口自检应该快速返回而不是卡住-I dummy那一行是判断「命令分发与参数解析是否正常」的最快手段它不碰任何硬件如果这一步就报参数错误说明问题出在上层而不是链路。这个自检习惯在改动命令表之后尤其有用能避免把编译期错误误判成网络问题。3.3 带内与带外两条通路的验证命令两条通路的语义完全不同验证方式也不一样。带内走本机内核驱动暴露的字符设备要求内核模块已加载带外走网络要求 BMC 已配置好管理口 IP 和账号权限。# 带内依赖 /dev/ipmi0通常需要 root 或 ipmi 组权限 ipmitool -I open mc info # 带外RMCPUDP 623账号需要有 Administrator 或 Operator 权限 ipmitool -I lanplus -H 192.0.2.10 -U operator -P REPLACE_ME mc infomc info是最小闭环它只发一条 Get Device ID返回固件版本、IPMI 规范版本、厂商 ID、设备支持的功能位。这条能通说明链路、认证、会话、命令编解码全部正常剩下所有命令失败都可以归因到「该命令需要的权限或固件支持缺失」排查范围立刻缩小一半。参数上有两点容易忽略-H后面只写地址不写协议头UDP 端口默认 623改过端口要用-p-P明文密码会进 shell history生产环境更稳妥的做法是配合-f从文件读或者用环境变量在脚本里传别把密码硬编码到巡检脚本里。3.4 交叉编译到 ARM 板卡目标板是 ARM 时难点不在编译本身而在 configure 的检测逻辑会尝试运行目标程序交叉环境下必然失败导致一堆特性被误判为不支持。./configure \ --hostaarch64-linux-gnu \ # 触发交叉编译模式 --disable-intf-open \ # 目标板内核未必有 OpenIPMI 驱动 --disable-intf-imb \ --enable-intf-lanplus \ --with-openssl/opt/sysroot-aarch64/usr # 指向目标架构的库别用宿主机默认路径 make -j8 file src/ipmitool # 确认架构是 aarch64 而不是 x86_64--host一旦指定configure 就进入交叉模式所有需要运行才能判断的检测都会退化成保守假设所以接口开关尽量显式写全不要依赖默认值。--with-openssl必须指向目标架构的 sysroot指错到宿主机路径时编译能过但链接会混入错误架构的库运行时报符号版本不匹配这类错误看信息完全联想不到是路径问题。4. 命令实现解剖sdr、sensor、sel 三者的数据流差异命令层看起来都是「发请求、打印结果」实际差别很大。mc info是单条请求sdr要先拉全量仓库再解析sensor要在原始值上做系数换算sel要处理分页和保留 ID。把这三条的数据流吃透基本就掌握了 IPMItool 的主要复杂度来源。4.1 sdr 的仓库解析与本地缓存SDR 是 BMC 里的一张描述表告诉上层「这台机器有哪些传感器、每个传感器的读数怎么解释、阈值是多少」。它不是一条命令能取完的先发 Get SDR Repository Info 拿到记录总数再按记录 ID 逐条 Get SDR每条 64 字节以内跨记录还要处理分片。ipmi_sdr.c的做法是先全量拉取再建索引缓存成按传感器编号可查的结构。这带来一个实用结论sdr命令在大机型上偏慢是正常的因为它一次性把所有记录都读回来了慢在协议往返次数而不是代码效率。反复调用sensor时如果每次都重新拉仓库延迟会很明显所以代码里有本地缓存但缓存生命周期只在单次进程内脚本里循环调用仍然是每条命令重拉一遍。工程上的应对办法是把sdr的输出重定向成文件做离线解析或者用sdr elist只看事件相关的精简视图减少一次性的数据量。别指望用并发压它——BMC 侧的处理能力通常很弱并发请求反而容易触发会话数超限。4.2 sensor 读数换算M、B、K1、K2 与原始值的对应传感器返回的是原始字节要变成摄氏度、伏特、转每分钟必须用 SDR 里记录的换算系数。这是整个项目里最容易算错的一段因为系数是带符号的小位宽字段指数还分两级。/* 对应 lib 层线性换算的核心逻辑去掉异常处理后的等价形式 */ double m sdr-analog.m; /* 斜率带符号 */ double b sdr-analog.b * pow(10, sdr-analog.k1); /* 偏移k1 是带符号 4 位指数 */ double k pow(10, sdr-analog.k2); /* 二级指数通常为 0 */ double value (m * raw b) * k; /* raw 为传感器读回的原始值 */ /* 非线性传感器如风扇、温度的部分类型走查表或分段公式不能套上式 */m为 0 表示该传感器不做线性换算此时原始值要按位解释成离散状态比如电源状态、设备存在位直接套公式会得到恒定的 0 或偏移值看起来像「传感器坏了」其实是解读方式选错了。k1是 4 位带符号数负指数表示缩小忽视符号会让读数差出几个数量级。判断换算是否正确的土办法拿sensor的输出和 BMC Web 界面或 BIOS 里的读数比对差一个固定倍数通常是k2处理错了差一个固定偏移通常是b的符号位读错。这类错误不会报错只会安静地给出错误数值属于采集系统里最危险的一类。4.3 sel 的分页读取与时间戳口径SEL 是事件日志容量从几十条到几千条不等。读取方式和 SDR 类似但多一层保护每次 Get SEL Entry 都要带上次返回的保留 IDBMC 用它防止读到一半时日志被新事件挤掉导致错位。ipmi_sel.c里的循环会持续检查保留 ID 是否变化变了就从头再来这个重试逻辑在日志写入频繁的机器上是必要的。ipmitool -I lanplus -H 192.0.2.10 -U operator -P REPLACE_ME sel info ipmitool -I lanplus -H 192.0.2.10 -U operator -P REPLACE_ME sel elist | head -20 ipmitool -I lanplus -H 192.0.2.10 -U operator -P REPLACE_ME sel time get时间戳是另一个高频误区。日志里存的是 BMC 自己时钟的秒数BMC 时钟和主机时钟是两套东西长期不同步的机器上差几小时很常见极端的会停在出厂时间。看到日志时间全是一年前先查sel time get和mc info里的时间别急着怀疑解析代码。修正手段是用sel time set把 BMC 时钟对齐注意这个操作在某些固件上需要管理员权限且会留下一条审计事件。4.4 raw 命令作为逃生舱标准命令覆盖不到的部分靠raw直接下发 netfn/cmd/数据。它把参数解析简化到极致第一个参数是网络功能号第二个是命令号后面全部按十六进制字节拼进请求数据区。# netfn 0x06 Appcmd 0x01 Get Device ID等价于 mc info 的底层请求 ipmitool -I lanplus -H 192.0.2.10 -U operator -P REPLACE_ME raw 0x06 0x01 # OEM 命令netfn 用厂商私有段数据区按固件文档逐字节拼 ipmitool -I lanplus -H 192.0.2.10 -U operator -P REPLACE_ME raw 0x30 0x21 0x01 0x00返回值的第一个字节是完成码0x00表示成功0xc1是不支持该命令0xcc是参数非法0xd4通常是权限或会话级别不够。调试 OEM 功能时先用标准命令确认通道可用再用 raw 逐字节试每次只改一个字节把「命令不支持」和「参数写错」区分开。5. 避坑记录五个让编译和采集同时翻车的点下面五条都是实际排查过的现象看起来五花八门根因集中在依赖检测、内核模块、时间口径三处。每条按现象、原因、解决写方便对照自己的环境。5.1 编译明明成功-I lanplus 却报接口不支持现象make无报错二进制也生成了执行带-I lanplus的命令直接提示不支持的接口-h的接口列表里也没有 lanplus。原因configure 阶段没检测到 TLS 库的开发文件或者压根没加--enable-intf-lanplus。这个跳过是静默的构建照样成功只是少编一个插件。解决先看 configure 结束时的特性汇总行确认 lanplus 状态再看config.log里对应库的检测记录。补装开发包后必须重新跑configure只make是没用的因为插件的编译开关写进了 Makefile。5.2 带内方式报找不到设备现象-I open报无法打开设备ls /dev/ipmi*什么都没有。原因内核的 IPMI 字符设备模块没加载或者设备被别的进程独占。容器里跑更容易遇到因为设备节点没被映射进去。解决先确认模块加载状态再确认设备节点存在最后确认当前用户在ipmi组或有 root 权限。lsmod | grep -i ipmi # 看 ipmi_si / ipmi_devintf 是否在 ls -l /dev/ipmi* # 有节点才有带内通路 id # 非 root 时确认所属组5.3 换了个构建环境加密相关代码编译报错现象同一份源码在一台机器上编得过换一台就在加密会话相关文件里报类型不完整或函数参数不匹配。原因TLS 库的大版本之间做过不透明化改造老代码直接访问结构体成员新库只暴露访问函数。源码分支和库版本没对齐就会这样。解决优先用与库版本匹配的源码分支不方便换分支时把--with-openssl指到一套兼容版本的库路径用独立的 sysroot 隔离不要动系统默认库。这类错误不要逐个函数去改改动面会失控。5.4 SEL 时间全是 1970 年或整体偏移现象sel elist打出来的事件时间明显不对要么停在 1970要么统一差若干小时。原因BMC 时钟未同步或者展示层做了时区换算而 BMC 存的是本地时间。不同固件对时间口径的处理不一致这是行业内公认的玄学区。解决先读原始时间戳和 BMC 当前时间做比对再决定是校时还是改展示逻辑。批量采集时建议原样保存 BMC 返回的秒数展示层再转换别在采集端就转成本地时间否则后续换时区要重采一遍。5.5 raw 命令返回 0xc1 或干脆超时现象raw下发的命令返回0xc1或者请求发出去没有任何响应直到超时。原因0xc1一般是 netfn/cmd 组合不被固件支持常见于把 OEM 命令发到了标准 netfn 段超时则多是通道权限不足或该通道被禁用。解决先用mc info确认通道和权限级别再用一条已知可用的标准命令验证链路最后才试 OEM 段。每次只改一个参数字节把「不支持」和「参数错」分开定位别一次改三个字节然后对着失败结果猜。6. 二次开发加一条自定义命令并跑通验证闭环改 IPMItool 最常见的需求是把自己板卡的 OEM 命令包装成一个语义化子命令避免每次手工拼 raw。改动面其实很小新建一个命令文件实现统一签名的入口函数在命令表里加一行在构建脚本里补一个源文件名。/* src/ipmi_mycmd.c一条最小可用的自定义命令 */ #include stdio.h #include ipmitool/ipmi_intf.h #include ipmitool/helper.h int ipmi_mycmd_main(struct ipmi_intf * intf, int argc, char ** argv) { struct ipmi_rs * rsp; struct ipmi_rq req {0}; req.msg.netfn 0x06; /* App 网络功能 */ req.msg.cmd 0x01; /* Get Device ID */ req.msg.data NULL; req.msg.data_len 0; rsp intf-sendrecv(intf, req); /* 走当前 -I 选定的接口 */ if (rsp NULL) { lprintf(LOG_ERR, no response from BMC); return -1; } if (rsp-ccode ! 0) { lprintf(LOG_ERR, command failed, ccode0x%02x, rsp-ccode); return -1; } printf(device%u firmware%u.%02u\n, rsp-data[1], rsp-data[4] 0x7f, rsp-data[5]); return 0; }然后在src/ipmitool.c的命令表里插一行{ mycmd, NULL, ipmi_mycmd_main, 自检示例 }注意插在哨兵项之前构建脚本的源文件列表里补上src/ipmi_mycmd.c。重新make后先用-I dummy跑一遍确认参数解析和分发没问题再上真机验证返回字段。6.1 验证闭环的三层顺序第一层是空接口自检ipmitool -I dummy mycmd应该快速返回而不是挂住能返回说明分发链通了。第二层是带内验证有本地驱动时用-I open跑能区分「协议实现对不对」和「网络配置对不对」。第三层才是带外-I lanplus -H ...上真机这一步失败基本可以锁定在认证、通道权限或目标固件不支持。三层跑完再去看返回字段。上面示例里rsp-data[4] 0x7f是在取主版本号高位是其他标志直接整个字节打印会得到离谱的版本号。这类位域细节不要凭感觉写对照规范里 Get Device ID 的响应布局逐位核对一次核对能省下后面几小时的困惑。写 OEM 命令包装时还有一条经验把 netfn、cmd、数据布局全部做成宏或常量表放在文件头部不要散落在函数体里。板卡固件升级后命令号变动是常事集中在一处改动比满文件搜十六进制数字靠谱得多。另外返回数据长度不要假设固定先判断rsp-data_len再取字节长度不足时打印告警而不是越界读取这类越界在嵌入式环境里可能直接让进程崩掉而崩溃现场往往看不出和命令有关。从那以后我每次往命令表里加条目都先make -j1全量编译一遍再用-I dummy空跑一次最后才连真机。这个习惯帮我省掉了好几次「网络明明通、数据却是空的」的复盘时间。希望帮到你。本文还有配套的精品资源点击获取