
简介这份源码面向服务器运维人员、基板管理控制器固件开发者以及学习智能平台管理接口协议的学生提供一套可编译、可追踪的命令行管理工具实现帮助理解主机如何与基板管理控制器交互并完成传感器读取、系统事件日志查看和电源状态控制等任务。压缩包共 171 个文件约 752KB以 49 个 C 源文件和 50 个头文件为主体辅以构建脚本和少量说明文档目录覆盖命令处理、协议实现、局域网与串口通信、传感器数据记录解析及机箱电源管理等模块。已有 552 人学习适合沿着命令目录、协议实现、数据记录、网络与串口通信等线索梳理请求发送、响应解析和命令分发流程也可作为新增命令、性能优化或特定硬件适配时的源码参考底稿。1. 机房凌晨的带外救火IPMItool 源码能解决什么凌晨两点一台机器 SSH 断了业务侧催着确认硬件状态远程只能走 BMC 的带外口。这种时候能用的工具不多IPMItool 是其中一个它通过 IPMI 协议和 BMC 对话能做电源开关、读传感器、拉 SEL 日志、看 FRU 资产、开 SOL 串口也能改 LAN 和用户配置。手里这份 IPMItool 源码的价值在于它不是只给你一个黑盒命令接口怎么抽象、会话怎么建、请求怎么封、不同厂商的兼容怎么绕都能翻到。适合运维、硬件测试、BMC 固件开发和做带外管理平台的开发者想改一个命令、加一条私有指令或者排查某个 BMC 为什么回错包从源码下手比猜命令快得多。2. 源码骨架与编译链路从目录布局到一次命令调用IPMItool 的代码量不算大但分层很清楚。第一次翻源码容易迷路因为接口插件、命令实现、工具函数混在几个目录里。先把骨架拆开src下是主程序和各类命令实现src/plugins下是传输接口include下是对外头文件lib下是公共工具。构建走 autotoolsconfigure一跑Makefile 生成主要依赖是 OpenSSL 和 readline——前者给 LANplus 做加密后者给交互模式做行编辑。这一章把目录、构建、调用链三件事捋顺后面看模块才不会散。2.1 目录布局接口层、命令层、工具层怎么分先看目录树这是读源码的第一张地图。不同版本的细节会有出入但主干结构基本稳定# IPMItool 源码主要目录布局以常见版本为例 ipmitool/ ├── src/ │ ├── ipmitool.c # 主入口解析全局参数、加载接口、分发命令 │ ├── ipmi_intf.c # 接口管理注册、查找、打开、关闭 │ ├── ipmi_sensor.c # sensor 命令实现 │ ├── ipmi_sel.c # SEL 日志读取与解析 │ ├── ipmi_fru.c # FRU 资产信息 │ ├── ipmi_sol.c # SOL 串口重定向 │ ├── ipmi_power.c # 电源控制 │ ├── ipmi_mc.c # BMC 管理控制器信息 │ └── plugins/ │ ├── lan/ # IPMI 1.5 LAN 接口 │ ├── lanplus/ # IPMI 2.0 LANplus带 RMCP 加密 │ ├── open/ # 本地开放接口 │ ├── bmc/ # 本地 KCS/系统接口 │ └── dummy/ # 空接口用于测试 ├── include/ │ └── ipmitool/ # 对外头文件ipmi_intf.h、ipmi_cc.h 等 ├── lib/ # 公共函数hexdump、log、内存、字符串 └── contrib/ # 补全脚本、打包脚本等附属物src下每个ipmi_xxx.c基本对应一类命令文件名就是命令名plugins下每个子目录是一种传输方式加新接口只动这里include/ipmitool/ipmi_intf.h是接口抽象的核心命令层和传输层靠它解耦lib放的是与业务无关的工具函数。这个布局的好处很直接加新命令只动src和命令表加新传输方式只动plugins互不牵连。读源码时建议先看ipmi_intf.h再看ipmitool.c的主流程最后挑一个命令文件跟到底比从头到尾顺序读效率高。2.2 构建系统与依赖autotools 那一套源码包如果自带configure可以直接配置如果只有configure.ac和Makefile.am先跑bootstrap.sh生成构建脚本。典型流程如下# 生成并执行构建脚本按需裁剪接口 ./bootstrap.sh ./configure --prefix/opt/ipmitool \ --enable-intf-lan \ --enable-intf-lanplus \ --enable-intf-open make -j$(nproc) sudo make install--enable-intf-*控制编译哪些接口插件裁掉不用的接口能减小体积也能避开对应依赖LANplus 依赖 OpenSSL交叉编译时要显式指定--with-openssl路径如果只做本地 KCS 管理可以不编 LAN 和 LANplus依赖会少一大截。安装后二进制在/opt/ipmitool/bin/ipmitool动态库路径要加ldconfig配置或设LD_LIBRARY_PATH否则运行时会报找不到libcrypto.so。注意不少发行版自带一份 ipmitool版本可能较老和新源码的命令行为有差异。联调时先用which ipmitool和ipmitool -V确认跑的是哪一份别调了半天发现调的是系统旧版。2.3 一次 ipmitool 命令的调用链主流程集中在src/ipmitool.c的main()里去掉配置读取和信号处理后主干就是五步/* 简化后的主流程对应 src/ipmitool.c 里的 main() */ int main(int argc, char **argv) { struct ipmi_intf *intf; /* 1. 解析全局参数-I 接口、-H 主机、-U 用户、-P 密码、-v 调试级别 */ parse_global_opts(argc, argv); /* 2. 按 -I 指定的名字查找并加载接口例如 lanplus */ intf ipmi_intf_load(interface_name); if (!intf) return EXIT_FAILURE; /* 3. 打开会话本地接口打开设备节点LAN 接口建会话 */ if (intf-open(intf) 0) return EXIT_FAILURE; /* 4. 按第一个非选项参数查命令表找到对应函数并调用 */ for (cmd ipmi_cmds; cmd-name; cmd) { if (strcmp(cmd-name, argv[optind]) 0) { rc cmd-func(intf, argc - optind, argv optind); break; } } /* 5. 关闭会话释放接口 */ intf-close(intf); return rc; }这段代码的关键点是接口先于命令加载命令函数只拿到intf指针不关心底层是本地设备还是网络会话所以同一套命令实现可以复用到所有接口上。参数含义-I决定intf类型-H、-U、-P是接口打开时用的连接参数-v控制日志级别-v叠加越多报文打印越细。命令表是二级分发的核心结构大致如下/* src/ipmitool.c 里的命令表片段字段因版本略有差异 */ static struct ipmi_cmd ipmi_cmds[] { { ipmi_power_main, power, 电源控制on/off/cycle/status }, { ipmi_sensor_main, sensor, 传感器读取list/get }, { ipmi_sel_main, sel, 系统事件日志list/clear }, { ipmi_fru_main, fru, FRU 资产信息print/read }, { ipmi_sol_main, sol, 串口重定向activate/deactivate }, { NULL, NULL, NULL } };主程序只负责找到函数指针具体子命令list、get、print由各命令自己的main再解析。加新命令就是加一行表项和一个实现函数这是后面二次开发最省事的入口。看懂这条链再去看任何一个命令文件都不会迷路。3. 核心模块拆解接口抽象、请求封装与命令分发这套代码最值得看的是抽象方式它把“怎么连 BMC”和“发什么命令”彻底拆开了。接口层只关心会话、认证、收发字节命令层只关心 netfn、cmd、data 怎么拼响应怎么解析。两边通过一个结构体和一个收发函数对接。理解这个分界改代码时就不会把协议逻辑写到命令文件里也不会把业务解析塞进传输插件。3.1 ipmi_intf把 KCS、LAN、LANplus 收进同一个结构体接口结构体是整个项目的抽象基类关键字段如下/* include/ipmitool/ipmi_intf.h 中接口结构的关键字段简化 */ struct ipmi_intf { const char *name; /* 接口名如 lanplus */ int (*open)(struct ipmi_intf *intf); /* 打开会话 */ void (*close)(struct ipmi_intf *intf); /* 关闭会话 */ int (*sendrecv)(struct ipmi_intf *intf, struct ipmi_rs *rsp, struct ipmi_rq *req); /* 同步请求/响应 */ int (*send_payload)(struct ipmi_intf *intf, ...); /* 带外 payload */ int (*recv_payload)(struct ipmi_intf *intf, ...); struct ipmi_session *session; /* 会话状态认证、加密、序列号 */ uint8_t target_addr; /* 目标地址桥接时用 */ uint8_t target_channel; uint8_t transit_addr; /* 两级桥接的中间地址 */ uint8_t transit_channel; int devnum; /* 本地设备号 */ int fd; /* 本地设备文件描述符 */ };open、close、sendrecv三个函数指针决定行为。LANplus 的sendrecv会做 RMCP 封装、加密、序列号校验本地 BMC 接口的sendrecv直接往/dev/ipmi0写 ioctldummy接口则把请求原样返回用来做单元测试。命令层只调用intf-sendrecv不关心底层是网络还是设备节点。参数里target_addr和target_channel用于跨 bridged 请求比如从 BMC 转发到机箱里的另一个控制器transit_addr和transit_channel是两级桥接的中间跳板devnum是本地设备编号多路 KCS 时用得上。加一种新传输方式实现这几个回调就能接进来命令层一行不用改。LANplus 的会话建立比本地接口复杂open里通常要依次完成Get Channel Authentication Capabilities 拿认证能力Open Session Request 建会话RAKP 1/2/3 做双向认证最后 Set Privilege Level 提权。任何一步失败open就返回错误后面的命令根本不会执行。所以遇到“连不上”先看-vvv输出停在哪个阶段比盲猜密码有用。3.2 请求与响应netfn、cmd、data 的三段式IPMI 请求的构造非常规整就是功能号、命令号、参数三段/* 构造一条 IPMI 请求的常见写法 */ struct ipmi_rq req; uint8_t data[2] { 0x01, 0x00 }; memset(req, 0, sizeof(req)); req.msg.netfn IPMI_NETFN_APP; /* 应用类命令如 Get Device ID */ req.msg.cmd 0x01; /* Get Device ID */ req.msg.data data; req.msg.data_len sizeof(data); if (intf-sendrecv(intf, rsp, req) 0) { /* 传输层失败超时、认证失败、链路错误 */ return -1; } if (rsp-ccode ! 0) { /* BMC 返回完成码非 0说明命令被拒绝或参数不对 */ return -1; }netfn决定命令大类比如0x06是应用类0x04是传感器/事件类0x0A是存储类cmd是大类下的具体命令data是参数长度按规范填。响应里多一个ccode完成码0x00表示成功0xC1是无效命令0xCB是请求数据长度不对这些码在include/ipmitool/ipmi_cc.h里有定义。实际写命令时先查规范确定 netfn 和 cmd再拼 data最后看 ccode基本就这三步。传输层失败和命令被拒绝要分开处理前者查网络和会话后者查参数和固件支持。3.3 命令实现传感器、SEL、FRU 各自怎么挂上去每个命令文件的入口模式几乎一样以 sensor 为例/* src/ipmi_sensor.c 中 sensor 命令的入口简化 */ int ipmi_sensor_main(struct ipmi_intf *intf, int argc, char **argv) { if (argc 1) return ipmi_sensor_list(intf); /* 不带子命令默认 list */ if (strcmp(argv[0], list) 0) return ipmi_sensor_list(intf); if (strcmp(argv[0], get) 0) return ipmi_sensor_get(intf, argv[1]); /* 子命令不认识打印用法 */ return usage(); }具体函数先构造rq再sendrecv最后解析rsp-data。传感器解析要按 SDR 记录格式拆字节SEL 要按事件记录格式拆FRU 要按区域头偏移读。这些解析代码是命令层最厚的部分也是和不同厂商固件差异打交道最多的地方。SEL 和 FRU 的差别值得单独说。SEL 读的是事件日志每条记录有固定头和时间戳解析难点在时间戳格式和记录类型FRU 读的是资产信息按区域组织每个区域有自己的头和字段长度解析难点在偏移计算和厂商自定义字段。传感器则是先读 SDR 仓库拿到传感器元数据再按元数据去读读数所以sensor list出的问题往往不在读数命令而在 SDR 读取阶段。看懂一个sensor list的完整解析其他命令基本能照葫芦画瓢。真正需要警惕的是厂商私有命令和扩展记录规范里没写只能靠抓包对比。4. 跑起来编译、连 BMC 与验证清单源码能读还不够得让它跑起来。这一章按“装依赖、编译、连 BMC、逐项验证”的顺序走每一步都给可抄的命令。建议在虚拟机或测试机上先跑通再去生产环境操作毕竟电源命令一执行就是真开关机。4.1 编译安装依赖、configure 参数与产物以常见 Linux 发行版为例先装构建依赖# Debian/Ubuntu 系装依赖 sudo apt-get install -y build-essential autoconf automake libtool \ libssl-dev libreadline-dev # 源码目录内执行 ./bootstrap.sh ./configure --prefix/opt/ipmitool --enable-intf-lanplus make -j$(nproc) sudo make install /opt/ipmitool/bin/ipmitool -Vlibssl-dev是 LANplus 的硬依赖libreadline-dev影响交互模式的行编辑和补全。--prefix装到独立目录避免和系统自带版本打架。make -j按 CPU 核数并行编译源码量不大一般一两分钟能完。装完用-V确认版本和编译进的接口列表输出里会列出 lan、lanplus、open 等接口名。如果是交叉编译--host指定目标三元组--with-openssl指向交叉编译好的 OpenSSL否则 configure 会找到宿主机上的库编出来的二进制在目标板上跑不了。4.2 连接 BMC本地、LAN、LANplus 三种姿势连接方式取决于你站在哪、BMC 开了什么# 本地 KCS机器上直接跑走 /dev/ipmi0 sudo ipmitool -I open mc info # 远程 IPMI 1.5 LANUDP 623明文认证 ipmitool -I lan -H 192.0.2.10 -U admin -P secret mc info # 远程 IPMI 2.0 LANplus加密生产环境优先 ipmitool -I lanplus -H 192.0.2.10 -U admin -P secret mc info-I指定接口-H是 BMC 地址-U、-P是 BMC 用户和密码。lan是 IPMI 1.5 的明文认证很多新固件默认关了lanplus是 2.0支持 RMCP 和加密套件跨网段更稳。密码不建议直接写在命令行可以用-f读文件或交互输入避免留在 shell history 里。本地open接口需要 root 或对/dev/ipmi0有权限容器里跑还要把设备节点映射进去。4.3 验证清单从 mc info 到 sol activate接手一台陌生 BMC我一般按从轻到重的顺序验证# 1. BMC 基本信息固件版本、IPMI 版本、厂商 ID ipmitool -I lanplus -H $BMC -U $USER -P $PASS mc info # 2. 电源状态与开关 ipmitool -I lanplus -H $BMC -U $USER -P $PASS power status # 3. 传感器列表确认 SDR 能读全 ipmitool -I lanplus -H $BMC -U $USER -P $PASS sensor list # 4. SEL 日志条数和最近几条 ipmitool -I lanplus -H $BMC -U $USER -P $PASS sel info ipmitool -I lanplus -H $BMC -U $USER -P $PASS sel list | tail -n 20 # 5. FRU 资产信息 ipmitool -I lanplus -H $BMC -U $USER -P $PASS fru print # 6. SOL 串口重定向退出用 ~. ipmitool -I lanplus -H $BMC -U $USER -P $PASS sol activatemc info能回来说明认证和网络没问题power status确认命令通道正常sensor list读不全往往是 SDR 或固件问题sel info报错常见于 SEL 满了或权限不够SOL 激活后终端进入串口模式退出是波浪号加点不是 CtrlC。每步失败都先用-vvv抓原始报文把“网络不通”和“命令不支持”分开。4.4 批量场景把连接参数收进巡检脚本单台调通后批量巡检是常见需求。把连接参数收进脚本别在命令行里反复粘贴#!/usr/bin/env bash # 批量读取多台 BMC 的电源状态和 SEL 条数 BMC_LISTbmc-list.txt USERmonitor PASS_FILE/etc/ipmi/monitor.pass while read -r host; do [ -z $host ] continue printf %s: $host timeout 10 ipmitool -I lanplus -H $host -U $USER -f $PASS_FILE \ power status 21 | tr -d \n printf | timeout 10 ipmitool -I lanplus -H $host -U $USER -f $PASS_FILE \ sel info 21 | grep -i entries | tr -d \n echo done $BMC_LIST-f从文件读密码避免进程列表泄露timeout防止单台 BMC 不通时脚本卡死tr -d \n把输出压成一行方便看。批量场景下别用-P明文也别把密码写进脚本这是血泪经验。脚本跑通后可以接监控把 SEL 条数突增和电源状态异常做成告警。5. 避坑与排查BMC 联调里最容易翻车的五件事带外联调的问题有个特点报错信息往往只有一句话真正原因藏在报文里。这一章按“现象、原因、解决”整理五条高频踩坑记录都是实际联调中反复遇到的。5.1 连接与认证类现象lanplus连不上报Error: Unable to establish IPMI v2 / RMCP session。原因常见三种——BMC 只开了 1.5 没开 2.0用户名密码错加密套件不匹配。少数固件对密码长度或特殊字符有限制也会表现成认证失败。解决先用-I lan试mc info确认 1.5 通不通确认 BMC 管理界面里 IPMI over LAN 是启用的用-C指定加密套件逐个试密码换成纯字母数字排除字符问题。排查命令# 看握手停在哪个阶段Open Session、RAKP、Set Privilege ipmitool -I lanplus -H $BMC -U $USER -P $PASS -vvv mc info 21 | head -n 40现象本地open接口报Could not open device /dev/ipmi0。原因内核模块没加载或者权限不够或者 BMC 驱动没绑定到设备。解决modprobe ipmi_devintf ipmi_si检查/dev/ipmi0是否存在lsmod看 ipmi 相关模块容器里跑要映射设备节点并加权限。如果设备节点存在但仍打不开看dmesg里有没有 KCS 超时或驱动加载失败。5.2 命令兼容与固件差异类现象sensor list只出一半或者某些传感器显示na。原因SDR 仓库里记录不完整或者固件对 SDR 读取分页处理有 bug也可能是工具侧的 SDR 缓存没刷新。解决先用sensor list -v看原始记录确认是读不到还是解析错对比 BMC 管理界面上显示的传感器数量必要时升级 BMC 固件。有些固件需要先sdr相关命令重新 dump 一遍缓存才会更新。现象sel list解析报错或者时间戳全是 1970 年。原因SEL 时间戳依赖 BMC 的 RTCBMC 没对时或者电池没电也有固件的事件记录格式不标准工具按规范解析就错位。解决先sel time get看 BMC 时间用sel time set校时解析错位就看-vvv原始字节对照规范确认是记录格式问题还是工具版本问题。时间戳问题在批量巡检里很常见脚本里最好把sel time get也纳入检查项。现象fru print某些字段乱码或为空。原因FRU 区域里的自定义字段没按规范填或者厂商用了私有格式工具按标准偏移读就串位。解决用fru read把原始二进制导出来按 FRU 规范手工拆找同型号正常机器对比。如果只有某一批机器乱码大概率是产线写 FRU 时字段长度填错了不是工具的问题。5.3 编译与运行时类现象configure报OpenSSL not found但系统明明装了 OpenSSL。原因只装了运行库没装头文件包或者交叉编译时pkg-config路径不对。解决装libssl-dev或openssl-devel交叉编译加--with-openssl路径和PKG_CONFIG_PATH。configure 的日志在config.log里搜openssl能看到具体检查了哪个头文件、哪个库。现象编出来的 ipmitool 跑起来报找不到libcrypto.so。原因装到了非标准前缀动态库不在默认搜索路径。解决加ldconfig配置或设LD_LIBRARY_PATH也可以用-Wl,-rpath把库路径编进二进制。生产环境建议后者少一个环境变量依赖部署时少一类故障。这五条覆盖了连接、命令、编译三个层面实际排查时按“先确认会话、再看 ccode、最后看原始报文”的顺序走大部分问题能在十分钟内定位。6. 二次开发加一个自定义命令并验证加命令是这套源码最实用的改造入口。假设要给团队加一个myinfo读 Get Device ID 并打印设备 ID顺便把厂商信息带出来。第一步在src下新建实现文件/* src/ipmi_myinfo.c一个最小自定义命令 */ #include ipmitool/ipmi.h #include ipmitool/ipmi_intf.h static int myinfo_run(struct ipmi_intf *intf) { struct ipmi_rs *rsp; struct ipmi_rq req {0}; req.msg.netfn IPMI_NETFN_APP; req.msg.cmd 0x01; /* Get Device ID */ rsp intf-sendrecv(intf, req); if (!rsp || rsp-ccode) { lprintf(LOG_ERR, Get Device ID failed, ccode0x%02x, rsp ? rsp-ccode : 0xff); return -1; } lprintf(LOG_INFO, device id: 0x%02x, rsp-data[0]); return 0; } int ipmi_myinfo_main(struct ipmi_intf *intf, int argc, char **argv) { if (argc 0) return usage(); return myinfo_run(intf); }第二步在src/ipmitool.c的命令表里加一行{ ipmi_myinfo_main, myinfo, 读取设备 ID }第三步把新文件加进src/Makefile.am的源文件列表重新autoreconf、configure、make。参数说明sendrecv第一个参数是接口指针第二个是响应结构第三个是请求结构ccode非 0 时先打日志再返回别直接吞掉否则线上排查只能靠猜。验证时先跑myinfo看输出再用-vvv对比原始报文确认 netfn 和 cmd 没拼错。改命令最容易翻车的地方是响应解析rsp-data的偏移和长度必须按规范来不同固件可能少填字段直接按固定下标读就会越界。我一般会先用ipmitool raw发同样的 netfn/cmd 拿原始字节确认长度后再写解析代码。从那以后我每次改命令都先用-vvv抓一遍原始报文再动代码验证完再提交。这份 IPMItool 源码我按接口层、命令层、工具层整理过需要的可以拿走对照着看先从ipmi_intf.h和ipmitool.c读起再挑一个命令跟到底比自己从头啃快得多。希望帮到你。本文还有配套的精品资源点击获取