ipmitool源码解析(一)——一次带内ipmitool raw data发送过程
之前有詳細講過,服務器實現IPMI智能管理需要從硬件和軟件層面提供支持,硬件需要嵌入式微控制器BMC,軟件上需要內核提供ipmi驅動以及用戶層與BMC交互的管理工具,今天就聊聊與BMC交互的工具ipmitool。服務器管理員可以通過ipmitool從BMC那邊獲取服務器的一些運行狀況。這樣的管理工具不僅僅只有ipmitool這一款,比如freeipmi以及各家根據標準ipmi編寫的工具,這些工具功能上大致一樣,ipmitool應該是目前使用最廣最好用的工具。現在RedFish API開始慢慢接替ipmitool,一般BMC都會同時支持ipmitool命令行和RedFish API。
ipmitool是一個開源軟件,所以它的安裝包一般就放有源碼,可以從網下免費下載,這里提供其中一個下載鏈接https://zh.osdn.net/projects/sfnet_ipmitool/downloads/ipmitool/1.8.18/ipmitool-1.8.18.tar.bz2/,以最新的Linux版ipitool-1.8.18為例,梳理一下ipmitool 一次收發raw data的過程。
首先,看看ipmitool這個軟件安裝包的目錄結構,看起來一大堆,我們只需要關注其中3個文件夾就好:include/ipmitool、lib、src,其他都是輔助文檔和安裝所需的文件,都可以忽略不看。include/ipmitool目錄下存放著ipmitool源碼的頭文件,這些頭文件是lib目錄里面的C文件需要引用的,基本一一對應,src是整個程序的入口,src/plugins里面包含了ipmi各個接口的定義(比如lan、lanplus、open、serial等),ipmitool將這些接口當做插件的形式調用。從大框架上一看,ipmitool的源碼結構是不是就簡單到一目了然了呢。
include目錄都是些頭文件,也不需要特別關注,在追溯源碼流程涉及到函數、結構體等具體聲明定義的時候到對應頭文件查看即可,所以真正需要傾注注意力去看的只剩下lib和src里面的內容了。
先來看看lib目錄,ipmi的核心源碼都在這兒了,不多,也就38個.c文件,每個C文件都對應BMC的某項功能,比如sdr、sel、sensor、fru等,這幾個是常用的不能再常用的內容了。這次只會用到ipmi_raw.c
再看看src,第一層目錄只需要看ipmitool.c文件,其他不看,第二層目錄plugins關注open目錄和ipmi_intf.c就行,帶內的raw data的發送和接收調用的是open這個接口,其他目錄對應的其他接口,不用看。
?????? ? ??
好了,講這么多就是為了簡化代碼,減少看到這么多文件時的壓力,下面進入正題。
1、基本調用關系
以下是ipmitool raw data發送過程的函數調用的流程圖,ipmitool.c中的main()函數是整個程序的入口,main函數中調用ipmi_main(),然后一級一級調用。
2、ipmitool.c
這里主要涉及到ipmi_cmd這個結構體,定義出自ipmi_intf.h。第一個成員是一個指向函數的指針,在C里稱為函數指針,最好先把概念理解清楚,不然后面會看不懂。
//ipmi_intf.hstruct ipmi_cmd {int (*func)(struct ipmi_intf * intf, int argc, char ** argv);const char * name;const char * desc; };ipmitool.c首先就創建并初始化了一個數組,數組里面的每個元素都是一個ipmi_cmd的結構體實例。
//ipmitool.cstruct ipmi_cmd ipmitool_cmd_list[] = {{ ipmi_raw_main, "raw", "Send a RAW IPMI request and print response" },{ ipmi_rawi2c_main, "i2c", "Send an I2C Master Write-Read command and print response" },{ ipmi_rawspd_main, "spd", "Print SPD info from remote I2C device" },{ ipmi_lanp_main, "lan", "Configure LAN Channels" },{ ipmi_chassis_main, "chassis", "Get chassis status and set power state" },{ ipmi_power_main, "power", "Shortcut to chassis power commands" },{ ipmi_event_main, "event", "Send pre-defined events to MC" },{ ipmi_mc_main, "mc", "Management Controller status and global enables" },{ ipmi_mc_main, "bmc", NULL }, /* for backwards compatibility */{ ipmi_sdr_main, "sdr", "Print Sensor Data Repository entries and readings" },{ ipmi_sensor_main, "sensor", "Print detailed sensor information" },{ ipmi_fru_main, "fru", "Print built-in FRU and scan SDR for FRU locators" },{ ipmi_gendev_main, "gendev", "Read/Write Device associated with Generic Device locators sdr" },{ ipmi_sel_main, "sel", "Print System Event Log (SEL)" },{ ipmi_pef_main, "pef", "Configure Platform Event Filtering (PEF)" },{ ipmi_sol_main, "sol", "Configure and connect IPMIv2.0 Serial-over-LAN" },{ ipmi_tsol_main, "tsol", "Configure and connect with Tyan IPMIv1.5 Serial-over-LAN" },{ ipmi_isol_main, "isol", "Configure IPMIv1.5 Serial-over-LAN" },{ ipmi_user_main, "user", "Configure Management Controller users" },{ ipmi_channel_main, "channel", "Configure Management Controller channels" },{ ipmi_session_main, "session", "Print session information" },{ ipmi_dcmi_main, "dcmi", "Data Center Management Interface"},{ ipmi_nm_main, "nm", "Node Manager Interface"},{ ipmi_sunoem_main, "sunoem", "OEM Commands for Sun servers" },{ ipmi_kontronoem_main, "kontronoem", "OEM Commands for Kontron devices"},{ ipmi_picmg_main, "picmg", "Run a PICMG/ATCA extended cmd"},{ ipmi_fwum_main, "fwum", "Update IPMC using Kontron OEM Firmware Update Manager" },{ ipmi_firewall_main,"firewall","Configure Firmware Firewall" },{ ipmi_delloem_main, "delloem", "OEM Commands for Dell systems" }, #ifdef HAVE_READLINE{ ipmi_shell_main, "shell", "Launch interactive IPMI shell" }, #endif{ ipmi_exec_main, "exec", "Run list of commands from file" },{ ipmi_set_main, "set", "Set runtime variable for shell and exec" },{ ipmi_echo_main, "echo", NULL }, /* for echoing lines to stdout in scripts */{ ipmi_hpmfwupg_main,"hpm", "Update HPM components using PICMG HPM.1 file"},{ ipmi_ekanalyzer_main,"ekanalyzer", "run FRU-Ekeying analyzer using FRU files"},{ ipmi_ime_main, "ime", "Update Intel Manageability Engine Firmware"},{ ipmi_vita_main, "vita", "Run a VITA 46.11 extended cmd"},{ ipmi_lan6_main, "lan6", "Configure IPv6 LAN Channels"},{ NULL }, };這里有個關于C語言的語法知識,數組的名字就是指向數組第一個元素的首地址,所以也可以認為數組的名字是指向這個數組的指針。這里將ipmitool_cmd_list就是ipmitool_cmd_list[]數組的指針。
//ipmitool.cint main(int argc, char ** argv) {int rc;rc = ipmi_main(argc, argv, ipmitool_cmd_list, NULL); //ipmitool_cmd_list就是指向ipmi_cmd_list[]數組的指針if (rc < 0)exit(EXIT_FAILURE);elseexit(EXIT_SUCCESS); }3、ipmitool_mian.c
跳到ipmi_main()函數的定義,這里涉及到接口的調用,ipmi_intf_load()函數就是用來加載用戶會調用哪個接口,這個是根據命令行中-I 后面的值決定的,帶內可以省略這個參數,默認就是open接口。
//ipmitool_main.c/* load interface */ipmi_main_intf = ipmi_intf_load(intfname);if (ipmi_main_intf == NULL) {lprintf(LOG_ERR, "Error loading interface %s", intfname);goto out_free;}4、ipmi_intf.c
ipmi_intf_table[]這個數組就是存放各個接口的。最后指向ipmi_intf_table[0]。
//ipmi_intf.cstruct ipmi_intf * ipmi_intf_load(char * name) {struct ipmi_intf ** intf;struct ipmi_intf * i;if (name == NULL) {i = ipmi_intf_table[0]; //這里表示如果沒有-I和后面的參數,默認取ipmi_intf_table[0]if (i->setup != NULL && (i->setup(i) < 0)) {lprintf(LOG_ERR, "Unable to setup ""interface %s", name);return NULL;}return i;}for (intf = ipmi_intf_table;((intf != NULL) && (*intf != NULL));intf++) {i = *intf;if (strncmp(name, i->name, strlen(name)) == 0) {if (i->setup != NULL && (i->setup(i) < 0)) {lprintf(LOG_ERR, "Unable to setup ""interface %s", name);return NULL;}return i;}}return NULL; }ipmi_intf_table[0]就是&ipmi_open_intf,是一個地址,這個地址指向了open.c中的ipmi_open_intf結構體。
//ipmi_intf.cstruct ipmi_intf * ipmi_intf_table[] = { #ifdef IPMI_INTF_OPEN&ipmi_open_intf, #endif #ifdef IPMI_INTF_IMB&ipmi_imb_intf, #endif #ifdef IPMI_INTF_LIPMI&ipmi_lipmi_intf, #endif #ifdef IPMI_INTF_BMC&ipmi_bmc_intf, #endif #ifdef IPMI_INTF_LAN&ipmi_lan_intf, #endif #ifdef IPMI_INTF_LANPLUS&ipmi_lanplus_intf, #endif #ifdef IPMI_INTF_FREE&ipmi_free_intf, #endif #ifdef IPMI_INTF_SERIAL&ipmi_serial_term_intf,&ipmi_serial_bm_intf, #endif #ifdef IPMI_INTF_DUMMY&ipmi_dummy_intf, #endif #ifdef IPMI_INTF_USB&ipmi_usb_intf, #endifNULL };5、open.c
ipmi_open_intf是ipmi_intf結構體的一個實例,下面是ipmi_open_intf的部分初始化,基本都是函數指針,指定了調用open接口會用到的一些函數。
//open.cstruct ipmi_intf ipmi_open_intf = {.name = "open",.desc = "Linux OpenIPMI Interface",.setup = ipmi_openipmi_setup,.open = ipmi_openipmi_open,.close = ipmi_openipmi_close,.sendrecv = ipmi_openipmi_send_cmd,.set_my_addr = ipmi_openipmi_set_my_addr,.my_addr = IPMI_BMC_SLAVE_ADDR,.target_addr = 0, /* init so -m local_addr does not cause bridging */ };ipmi_intf結構體如下,成員眾多,上面的實例ipmi_open_intf只為其成員賦值了一部分。
//ipmi_intf.hstruct ipmi_intf {char name[16];char desc[128];char *devfile;int fd;int opened;int abort;int noanswer;int picmg_avail;int vita_avail;IPMI_OEM manufacturer_id;int ai_family;struct ipmi_session_params ssn_params;struct ipmi_session * session;struct ipmi_oem_handle * oem;struct ipmi_cmd * cmdlist;uint8_t target_ipmb_addr;uint32_t my_addr;uint32_t target_addr;uint8_t target_lun;uint8_t target_channel;uint32_t transit_addr;uint8_t transit_channel;uint16_t max_request_data_size;uint16_t max_response_data_size;uint8_t devnum;int (*setup)(struct ipmi_intf * intf);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 (*sendrsp)(struct ipmi_intf * intf, struct ipmi_rs * rsp);struct ipmi_rs *(*recv_sol)(struct ipmi_intf * intf);struct ipmi_rs *(*send_sol)(struct ipmi_intf * intf, struct ipmi_v2_payload * payload);int (*keepalive)(struct ipmi_intf * intf);int (*set_my_addr)(struct ipmi_intf * intf, uint8_t addr);void (*set_max_request_data_size)(struct ipmi_intf * intf, uint16_t size);void (*set_max_response_data_size)(struct ipmi_intf * intf, uint16_t size); };6、ipmi_main.c
加載open接口后,ipmi_main()中調用了ipmi_cmd_run()
//ipmi_mian.cint ipmi_cmd_run(struct ipmi_intf * intf, char * name, int argc, char ** argv) {struct ipmi_cmd * cmd = intf->cmdlist; //intf->cmdlist指向的就是ipmitool_cmd_list[]數組/* hook to run a default command if nothing specified */if (name == NULL) {if (cmd->func == NULL || cmd->name == NULL)return -1;else if (strncmp(cmd->name, "default", 7) == 0)return cmd->func(intf, 0, NULL);else {lprintf(LOG_ERR, "No command provided!");ipmi_cmd_print(intf->cmdlist);return -1;}}for (cmd=intf->cmdlist; cmd->func != NULL; cmd++) {if (strncmp(name, cmd->name, __maxlen(cmd->name, name)) == 0)break;}if (cmd->func == NULL) {cmd = intf->cmdlist;if (strncmp(cmd->name, "default", 7) == 0)return cmd->func(intf, argc+1, argv-1);lprintf(LOG_ERR, "Invalid command: %s", name);ipmi_cmd_print(intf->cmdlist);return -1;}return cmd->func(intf, argc, argv); }中間的一個for循環很關鍵,這里遍歷的是ipmitool_cmd_list[]這個數組,根據你傳入的值(這里是raw),確定調用對應的函數。比如我今天發送的是 raw data,用的命令是ipmitool raw 0x06 0x01,這里的name就是等于raw,根據raw,cmd->func指向的函數名就是ipmi_raw_main,接著跳到這個ipmi_raw_main()這個函數中繼續執行。
for (cmd=intf->cmdlist; cmd->func != NULL; cmd++) {if (strncmp(name, cmd->name, __maxlen(cmd->name, name)) == 0)break;}7、ipmi_raw.c
這里主要弄明白intf->sendrecv指向的是哪個函數就一目了然了,這個在上面已經提到過,ipmi_open_intf中已經賦值過了(.sendrecv = ipmi_openipmi_send_cmd)。正常情況下rsp就是BMC返回的raw data。
//ipmi_raw.cint ipmi_raw_main(struct ipmi_intf * intf, int argc, char ** argv) {struct ipmi_rs * rsp;struct ipmi_rq req;uint8_t netfn, cmd, lun;uint16_t netfn_tmp = 0;int i;uint8_t data[256];if (argc == 1 && strncmp(argv[0], "help", 4) == 0) {ipmi_raw_help();return 0;}else if (argc < 2) {lprintf(LOG_ERR, "Not enough parameters given.");ipmi_raw_help();return (-1);}else if (argc > sizeof(data)){lprintf(LOG_NOTICE, "Raw command input limit (256 bytes) exceeded");return -1;}lun = intf->target_lun;netfn_tmp = str2val(argv[0], ipmi_netfn_vals);if (netfn_tmp == 0xff) {if (is_valid_param(argv[0], &netfn, "netfn") != 0)return (-1);} else {if (netfn_tmp >= UINT8_MAX) {lprintf(LOG_ERR, "Given netfn \"%s\" is out of range.", argv[0]);return (-1);}netfn = netfn_tmp;}if (is_valid_param(argv[1], &cmd, "command") != 0)return (-1);memset(data, 0, sizeof(data));memset(&req, 0, sizeof(req));req.msg.netfn = netfn;req.msg.lun = lun;req.msg.cmd = cmd;req.msg.data = data;for (i=2; i<argc; i++) {uint8_t val = 0;if (is_valid_param(argv[i], &val, "data") != 0)return (-1);req.msg.data[i-2] = val;req.msg.data_len++;}lprintf(LOG_INFO, "RAW REQ (channel=0x%x netfn=0x%x lun=0x%x cmd=0x%x data_len=%d)",intf->target_channel & 0x0f, req.msg.netfn,req.msg.lun , req.msg.cmd, req.msg.data_len);printbuf(req.msg.data, req.msg.data_len, "RAW REQUEST");rsp = intf->sendrecv(intf, &req);if (rsp == NULL) {lprintf(LOG_ERR, "Unable to send RAW command ""(channel=0x%x netfn=0x%x lun=0x%x cmd=0x%x)",intf->target_channel & 0x0f, req.msg.netfn, req.msg.lun, req.msg.cmd);return -1;}if (rsp->ccode > 0) {lprintf(LOG_ERR, "Unable to send RAW command ""(channel=0x%x netfn=0x%x lun=0x%x cmd=0x%x rsp=0x%x): %s",intf->target_channel & 0x0f, req.msg.netfn, req.msg.lun, req.msg.cmd, rsp->ccode,val2str(rsp->ccode, completion_code_vals));return -1;}lprintf(LOG_INFO, "RAW RSP (%d bytes)", rsp->data_len);/* print the raw response buffer */for (i=0; i<rsp->data_len; i++) {if (((i%16) == 0) && (i != 0))printf("\n");printf(" %2.2x", rsp->data[i]);}printf("\n");return 0; }ipmi_openipmi_send_cmd()函數中,調用了ioctrl()實現數據的發送和接收。ioctrl()有點兒類似于linux中的read()和write(),可以說是一種特殊的read()和write()的組合,read()和write()不能實現的讀寫操作,通過ioctrl()可以實現,這里就不介紹了,沒幾頁紙也寫不完,說不透。
//open.cif (ioctl(intf->fd, IPMICTL_SEND_COMMAND, &_req) < 0) {lperror(LOG_ERR, "Unable to send command");if (data != NULL) {free(data);data = NULL;}return NULL;} //open.c/* get data */if (ioctl(intf->fd, IPMICTL_RECEIVE_MSG_TRUNC, &recv) < 0) {lperror(LOG_ERR, "Error receiving message");if (errno != EMSGSIZE) {if (data != NULL) {free(data);data = NULL;}return NULL;}}ipmitool 一次raw data的發送大致過程就是這樣,細節可再細看。
總結
以上是生活随笔為你收集整理的ipmitool源码解析(一)——一次带内ipmitool raw data发送过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 快捷键调出计算机桌面小工具,win7桌面
- 下一篇: 格拉布斯检验法matlab,格拉布斯检验