Zdenek Styblik 23e9340b49 ID:302 - HPM.2 long message support
This patch adds basic long message support for PICMG-based systems according to
the HPM.2 specification.
It also introduces APIs for setting inbound and outbound messages sizes per
selected interface.
This APIs are used in LAN and LAN+ interfaces to set autonomously detected
inbound and outbound message sizes.
The newly introduced APIs also replace the existing message size detection code
in several ipmitool commands in order to leverage the advantages of long message
support (HPM.1 upgrade, SDR acquring, FRU inventory read and write).
The Kontron-specific long message support is moved under a OEM option.

Commit for Dmitry Bazhenov
2014-04-08 15:18:50 +02:00

2113 lines
55 KiB
C

/*
* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind.
* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
* SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE
* FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
* OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
* SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,
* OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
* PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF
* LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*/
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>
#include <ipmitool/helper.h>
#include <ipmitool/log.h>
#include <ipmitool/bswap.h>
#include <ipmitool/ipmi.h>
#include <ipmitool/ipmi_sel.h>
#include <ipmitool/ipmi_intf.h>
#include <ipmitool/ipmi_oem.h>
#include <ipmitool/ipmi_strings.h>
#include <ipmitool/ipmi_constants.h>
#include <ipmitool/hpm2.h>
#if HAVE_CONFIG_H
# include <config.h>
#endif
#include "lan.h"
#include "rmcp.h"
#include "asf.h"
#include "auth.h"
#define IPMI_LAN_TIMEOUT 2
#define IPMI_LAN_RETRY 4
#define IPMI_LAN_PORT 0x26f
#define IPMI_LAN_CHANNEL_E 0x0e
/*
* LAN interface is required to support 45 byte request transactions and
* 42 byte response transactions.
*/
#define IPMI_LAN_MAX_REQUEST_SIZE 38 /* 45 - 7 */
#define IPMI_LAN_MAX_RESPONSE_SIZE 34 /* 42 - 8 */
extern const struct valstr ipmi_privlvl_vals[];
extern const struct valstr ipmi_authtype_session_vals[];
extern int verbose;
struct ipmi_rq_entry * ipmi_req_entries;
static struct ipmi_rq_entry * ipmi_req_entries_tail;
static uint8_t bridge_possible = 0;
static int ipmi_lan_send_packet(struct ipmi_intf * intf, uint8_t * data, int data_len);
static struct ipmi_rs * ipmi_lan_recv_packet(struct ipmi_intf * intf);
static struct ipmi_rs * ipmi_lan_poll_recv(struct ipmi_intf * intf);
static int ipmi_lan_setup(struct ipmi_intf * intf);
static int ipmi_lan_keepalive(struct ipmi_intf * intf);
static struct ipmi_rs * ipmi_lan_recv_sol(struct ipmi_intf * intf);
static struct ipmi_rs * ipmi_lan_send_sol(struct ipmi_intf * intf,
struct ipmi_v2_payload * payload);
static struct ipmi_rs * ipmi_lan_send_cmd(struct ipmi_intf * intf, struct ipmi_rq * req);
static int ipmi_lan_send_rsp(struct ipmi_intf * intf, struct ipmi_rs * rsp);
static int ipmi_lan_open(struct ipmi_intf * intf);
static void ipmi_lan_close(struct ipmi_intf * intf);
static int ipmi_lan_ping(struct ipmi_intf * intf);
static void ipmi_lan_set_max_rq_data_size(struct ipmi_intf * intf, uint16_t size);
static void ipmi_lan_set_max_rp_data_size(struct ipmi_intf * intf, uint16_t size);
struct ipmi_intf ipmi_lan_intf = {
name: "lan",
desc: "IPMI v1.5 LAN Interface",
setup: ipmi_lan_setup,
open: ipmi_lan_open,
close: ipmi_lan_close,
sendrecv: ipmi_lan_send_cmd,
sendrsp: ipmi_lan_send_rsp,
recv_sol: ipmi_lan_recv_sol,
send_sol: ipmi_lan_send_sol,
keepalive: ipmi_lan_keepalive,
set_max_request_data_size: ipmi_lan_set_max_rq_data_size,
set_max_response_data_size: ipmi_lan_set_max_rp_data_size,
target_addr: IPMI_BMC_SLAVE_ADDR,
};
static struct ipmi_rq_entry *
ipmi_req_add_entry(struct ipmi_intf * intf, struct ipmi_rq * req, uint8_t req_seq)
{
struct ipmi_rq_entry * e;
e = malloc(sizeof(struct ipmi_rq_entry));
if (e == NULL) {
lprintf(LOG_ERR, "ipmitool: malloc failure");
return NULL;
}
memset(e, 0, sizeof(struct ipmi_rq_entry));
memcpy(&e->req, req, sizeof(struct ipmi_rq));
e->intf = intf;
e->rq_seq = req_seq;
if (ipmi_req_entries == NULL)
ipmi_req_entries = e;
else
ipmi_req_entries_tail->next = e;
ipmi_req_entries_tail = e;
lprintf(LOG_DEBUG+3, "added list entry seq=0x%02x cmd=0x%02x",
e->rq_seq, e->req.msg.cmd);
return e;
}
static struct ipmi_rq_entry *
ipmi_req_lookup_entry(uint8_t seq, uint8_t cmd)
{
struct ipmi_rq_entry * e = ipmi_req_entries;
while (e && (e->rq_seq != seq || e->req.msg.cmd != cmd)) {
if (e->next == NULL || e == e->next)
return NULL;
e = e->next;
}
return e;
}
static void
ipmi_req_remove_entry(uint8_t seq, uint8_t cmd)
{
struct ipmi_rq_entry * p, * e, * saved_next_entry;
e = p = ipmi_req_entries;
while (e && (e->rq_seq != seq || e->req.msg.cmd != cmd)) {
p = e;
e = e->next;
}
if (e) {
lprintf(LOG_DEBUG+3, "removed list entry seq=0x%02x cmd=0x%02x",
seq, cmd);
saved_next_entry = e->next;
p->next = (p->next == e->next) ? NULL : e->next;
/* If entry being removed is first in list, fix up list head */
if (ipmi_req_entries == e) {
if (ipmi_req_entries != p)
ipmi_req_entries = p;
else
ipmi_req_entries = saved_next_entry;
}
/* If entry being removed is last in list, fix up list tail */
if (ipmi_req_entries_tail == e) {
if (ipmi_req_entries_tail != p)
ipmi_req_entries_tail = p;
else
ipmi_req_entries_tail = NULL;
}
if (e->msg_data) {
free(e->msg_data);
e->msg_data = NULL;
}
free(e);
e = NULL;
}
}
static void
ipmi_req_clear_entries(void)
{
struct ipmi_rq_entry * p, * e;
e = ipmi_req_entries;
while (e) {
lprintf(LOG_DEBUG+3, "cleared list entry seq=0x%02x cmd=0x%02x",
e->rq_seq, e->req.msg.cmd);
if (e->next != NULL) {
p = e->next;
free(e);
e = p;
} else {
free(e);
e = NULL;
break;
}
}
ipmi_req_entries = NULL;
}
static int
get_random(void *data, int len)
{
int fd = open("/dev/urandom", O_RDONLY);
int rv;
if (fd < 0)
return errno;
if (len < 0) {
close(fd);
return errno; /* XXX: ORLY? */
}
rv = read(fd, data, len);
close(fd);
return rv;
}
static int
ipmi_lan_send_packet(struct ipmi_intf * intf, uint8_t * data, int data_len)
{
if (verbose > 2)
printbuf(data, data_len, "send_packet");
return send(intf->fd, data, data_len, 0);
}
static struct ipmi_rs *
ipmi_lan_recv_packet(struct ipmi_intf * intf)
{
static struct ipmi_rs rsp;
fd_set read_set, err_set;
struct timeval tmout;
int ret;
FD_ZERO(&read_set);
FD_SET(intf->fd, &read_set);
FD_ZERO(&err_set);
FD_SET(intf->fd, &err_set);
tmout.tv_sec = intf->session->timeout;
tmout.tv_usec = 0;
ret = select(intf->fd + 1, &read_set, NULL, &err_set, &tmout);
if (ret < 0 || FD_ISSET(intf->fd, &err_set) || !FD_ISSET(intf->fd, &read_set))
return NULL;
/* the first read may return ECONNREFUSED because the rmcp ping
* packet--sent to UDP port 623--will be processed by both the
* BMC and the OS.
*
* The problem with this is that the ECONNREFUSED takes
* priority over any other received datagram; that means that
* the Connection Refused shows up _before_ the response packet,
* regardless of the order they were sent out. (unless the
* response is read before the connection refused is returned)
*/
ret = recv(intf->fd, &rsp.data, IPMI_BUF_SIZE, 0);
if (ret < 0) {
FD_ZERO(&read_set);
FD_SET(intf->fd, &read_set);
FD_ZERO(&err_set);
FD_SET(intf->fd, &err_set);
tmout.tv_sec = intf->session->timeout;
tmout.tv_usec = 0;
ret = select(intf->fd + 1, &read_set, NULL, &err_set, &tmout);
if (ret < 0 || FD_ISSET(intf->fd, &err_set) || !FD_ISSET(intf->fd, &read_set))
return NULL;
ret = recv(intf->fd, &rsp.data, IPMI_BUF_SIZE, 0);
if (ret < 0)
return NULL;
}
if (ret == 0)
return NULL;
rsp.data[ret] = '\0';
rsp.data_len = ret;
if (verbose > 2)
printbuf(rsp.data, rsp.data_len, "recv_packet");
return &rsp;
}
/*
* parse response RMCP "pong" packet
*
* return -1 if ping response not received
* returns 0 if IPMI is NOT supported
* returns 1 if IPMI is supported
*
* udp.source = 0x026f // RMCP_UDP_PORT
* udp.dest = ? // udp.source from rmcp-ping
* udp.len = ?
* udp.check = ?
* rmcp.ver = 0x06 // RMCP Version 1.0
* rmcp.__res = 0x00 // RESERVED
* rmcp.seq = 0xff // no RMCP ACK
* rmcp.class = 0x06 // RMCP_CLASS_ASF
* asf.iana = 0x000011be // ASF_RMCP_IANA
* asf.type = 0x40 // ASF_TYPE_PONG
* asf.tag = ? // asf.tag from rmcp-ping
* asf.__res = 0x00 // RESERVED
* asf.len = 0x10 // 16 bytes
* asf.data[3:0]= 0x000011be // IANA# = RMCP_ASF_IANA if no OEM
* asf.data[7:4]= 0x00000000 // OEM-defined (not for IPMI)
* asf.data[8] = 0x81 // supported entities
* // [7]=IPMI [6:4]=RES [3:0]=ASF_1.0
* asf.data[9] = 0x00 // supported interactions (reserved)
* asf.data[f:a]= 0x000000000000
*/
static int
ipmi_handle_pong(struct ipmi_intf * intf, struct ipmi_rs * rsp)
{
struct rmcp_pong * pong;
if (rsp == NULL)
return -1;
pong = (struct rmcp_pong *)rsp->data;
lprintf(LOG_DEBUG,
"Received IPMI/RMCP response packet: \n"
" IPMI%s Supported\n"
" ASF Version %s\n"
" RMCP Version %s\n"
" RMCP Sequence %d\n"
" IANA Enterprise %ld\n",
(pong->sup_entities & 0x80) ? "" : " NOT",
(pong->sup_entities & 0x01) ? "1.0" : "unknown",
(pong->rmcp.ver == 6) ? "1.0" : "unknown",
pong->rmcp.seq,
ntohl(pong->iana));
return (pong->sup_entities & 0x80) ? 1 : 0;
}
/* build and send RMCP presence ping packet
*
* RMCP ping
*
* udp.source = ?
* udp.dest = 0x026f // RMCP_UDP_PORT
* udp.len = ?
* udp.check = ?
* rmcp.ver = 0x06 // RMCP Version 1.0
* rmcp.__res = 0x00 // RESERVED
* rmcp.seq = 0xff // no RMCP ACK
* rmcp.class = 0x06 // RMCP_CLASS_ASF
* asf.iana = 0x000011be // ASF_RMCP_IANA
* asf.type = 0x80 // ASF_TYPE_PING
* asf.tag = ? // ASF sequence number
* asf.__res = 0x00 // RESERVED
* asf.len = 0x00
*
*/
static int
ipmi_lan_ping(struct ipmi_intf * intf)
{
struct asf_hdr asf_ping = {
.iana = htonl(ASF_RMCP_IANA),
.type = ASF_TYPE_PING,
};
struct rmcp_hdr rmcp_ping = {
.ver = RMCP_VERSION_1,
.class = RMCP_CLASS_ASF,
.seq = 0xff,
};
uint8_t * data;
int len = sizeof(rmcp_ping) + sizeof(asf_ping);
int rv;
data = malloc(len);
if (data == NULL) {
lprintf(LOG_ERR, "ipmitool: malloc failure");
return -1;
}
memset(data, 0, len);
memcpy(data, &rmcp_ping, sizeof(rmcp_ping));
memcpy(data+sizeof(rmcp_ping), &asf_ping, sizeof(asf_ping));
lprintf(LOG_DEBUG, "Sending IPMI/RMCP presence ping packet");
rv = ipmi_lan_send_packet(intf, data, len);
free(data);
data = NULL;
if (rv < 0) {
lprintf(LOG_ERR, "Unable to send IPMI presence ping packet");
return -1;
}
if (ipmi_lan_poll_recv(intf) == 0)
return 0;
return 1;
}
/*
* The "thump" functions are used to send an extra packet following each
* request message. This may kick-start some BMCs that get confused with
* bad passwords or operate poorly under heavy network load.
*/
static void
ipmi_lan_thump_first(struct ipmi_intf * intf)
{
/* is this random data? */
uint8_t data[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x20, 0x18, 0xc8, 0xc2, 0x01, 0x01, 0x3c };
ipmi_lan_send_packet(intf, data, 16);
}
static void
ipmi_lan_thump(struct ipmi_intf * intf)
{
uint8_t data[10] = "thump";
ipmi_lan_send_packet(intf, data, 10);
}
static struct ipmi_rs *
ipmi_lan_poll_recv(struct ipmi_intf * intf)
{
struct rmcp_hdr rmcp_rsp;
struct ipmi_rs * rsp;
struct ipmi_rq_entry * entry;
int x=0, rv;
uint8_t our_address = intf->my_addr;
if (our_address == 0)
our_address = IPMI_BMC_SLAVE_ADDR;
rsp = ipmi_lan_recv_packet(intf);
while (rsp != NULL) {
/* parse response headers */
memcpy(&rmcp_rsp, rsp->data, 4);
switch (rmcp_rsp.class) {
case RMCP_CLASS_ASF:
/* ping response packet */
rv = ipmi_handle_pong(intf, rsp);
return (rv <= 0) ? NULL : rsp;
case RMCP_CLASS_IPMI:
/* handled by rest of function */
break;
default:
lprintf(LOG_DEBUG, "Invalid RMCP class: %x",
rmcp_rsp.class);
rsp = ipmi_lan_recv_packet(intf);
continue;
}
x = 4;
rsp->session.authtype = rsp->data[x++];
memcpy(&rsp->session.seq, rsp->data+x, 4);
x += 4;
memcpy(&rsp->session.id, rsp->data+x, 4);
x += 4;
if (rsp->session.id == (intf->session->session_id + 0x10000000)) {
/* With SOL, authtype is always NONE, so we have no authcode */
rsp->session.payloadtype = IPMI_PAYLOAD_TYPE_SOL;
rsp->session.msglen = rsp->data[x++];
rsp->payload.sol_packet.packet_sequence_number =
rsp->data[x++] & 0x0F;
rsp->payload.sol_packet.acked_packet_number =
rsp->data[x++] & 0x0F;
rsp->payload.sol_packet.accepted_character_count =
rsp->data[x++];
rsp->payload.sol_packet.is_nack =
rsp->data[x] & 0x40;
rsp->payload.sol_packet.transfer_unavailable =
rsp->data[x] & 0x20;
rsp->payload.sol_packet.sol_inactive =
rsp->data[x] & 0x10;
rsp->payload.sol_packet.transmit_overrun =
rsp->data[x] & 0x08;
rsp->payload.sol_packet.break_detected =
rsp->data[x++] & 0x04;
x++; /* On ISOL there's and additional fifth byte before the data starts */
lprintf(LOG_DEBUG, "SOL sequence number : 0x%02x",
rsp->payload.sol_packet.packet_sequence_number);
lprintf(LOG_DEBUG, "SOL acked packet : 0x%02x",
rsp->payload.sol_packet.acked_packet_number);
lprintf(LOG_DEBUG, "SOL accepted char count : 0x%02x",
rsp->payload.sol_packet.accepted_character_count);
lprintf(LOG_DEBUG, "SOL is nack : %s",
rsp->payload.sol_packet.is_nack? "true" : "false");
lprintf(LOG_DEBUG, "SOL xfer unavailable : %s",
rsp->payload.sol_packet.transfer_unavailable? "true" : "false");
lprintf(LOG_DEBUG, "SOL inactive : %s",
rsp->payload.sol_packet.sol_inactive? "true" : "false");
lprintf(LOG_DEBUG, "SOL transmit overrun : %s",
rsp->payload.sol_packet.transmit_overrun? "true" : "false");
lprintf(LOG_DEBUG, "SOL break detected : %s",
rsp->payload.sol_packet.break_detected? "true" : "false");
}
else
{
/* Standard IPMI 1.5 packet */
rsp->session.payloadtype = IPMI_PAYLOAD_TYPE_IPMI;
if (intf->session->active && (rsp->session.authtype || intf->session->authtype))
x += 16;
rsp->session.msglen = rsp->data[x++];
rsp->payload.ipmi_response.rq_addr = rsp->data[x++];
rsp->payload.ipmi_response.netfn = rsp->data[x] >> 2;
rsp->payload.ipmi_response.rq_lun = rsp->data[x++] & 0x3;
x++; /* checksum */
rsp->payload.ipmi_response.rs_addr = rsp->data[x++];
rsp->payload.ipmi_response.rq_seq = rsp->data[x] >> 2;
rsp->payload.ipmi_response.rs_lun = rsp->data[x++] & 0x3;
rsp->payload.ipmi_response.cmd = rsp->data[x++];
rsp->ccode = rsp->data[x++];
if (verbose > 2)
printbuf(rsp->data, rsp->data_len, "ipmi message header");
lprintf(LOG_DEBUG+1, "<< IPMI Response Session Header");
lprintf(LOG_DEBUG+1, "<< Authtype : %s",
val2str(rsp->session.authtype, ipmi_authtype_session_vals));
lprintf(LOG_DEBUG+1, "<< Sequence : 0x%08lx",
(long)rsp->session.seq);
lprintf(LOG_DEBUG+1, "<< Session ID : 0x%08lx",
(long)rsp->session.id);
lprintf(LOG_DEBUG+1, "<< IPMI Response Message Header");
lprintf(LOG_DEBUG+1, "<< Rq Addr : %02x",
rsp->payload.ipmi_response.rq_addr);
lprintf(LOG_DEBUG+1, "<< NetFn : %02x",
rsp->payload.ipmi_response.netfn);
lprintf(LOG_DEBUG+1, "<< Rq LUN : %01x",
rsp->payload.ipmi_response.rq_lun);
lprintf(LOG_DEBUG+1, "<< Rs Addr : %02x",
rsp->payload.ipmi_response.rs_addr);
lprintf(LOG_DEBUG+1, "<< Rq Seq : %02x",
rsp->payload.ipmi_response.rq_seq);
lprintf(LOG_DEBUG+1, "<< Rs Lun : %01x",
rsp->payload.ipmi_response.rs_lun);
lprintf(LOG_DEBUG+1, "<< Command : %02x",
rsp->payload.ipmi_response.cmd);
lprintf(LOG_DEBUG+1, "<< Compl Code : 0x%02x",
rsp->ccode);
/* now see if we have outstanding entry in request list */
entry = ipmi_req_lookup_entry(rsp->payload.ipmi_response.rq_seq,
rsp->payload.ipmi_response.cmd);
if (entry) {
lprintf(LOG_DEBUG+2, "IPMI Request Match found");
if ((intf->target_addr != our_address) && bridge_possible) {
if ((rsp->data_len) && (rsp->payload.ipmi_response.netfn == 7) &&
(rsp->payload.ipmi_response.cmd != 0x34)) {
if (verbose > 2)
printbuf(&rsp->data[x], rsp->data_len-x,
"bridge command response");
}
/* bridged command: lose extra header */
if (entry->bridging_level &&
rsp->payload.ipmi_response.netfn == 7 &&
rsp->payload.ipmi_response.cmd == 0x34) {
entry->bridging_level--;
if (rsp->data_len - x - 1 == 0) {
rsp = !rsp->ccode ? ipmi_lan_recv_packet(intf) : NULL;
if (!entry->bridging_level)
entry->req.msg.cmd = entry->req.msg.target_cmd;
if (rsp == NULL) {
ipmi_req_remove_entry(entry->rq_seq, entry->req.msg.cmd);
}
continue;
} else {
/* The bridged answer data are inside the incoming packet */
memmove(rsp->data + x - 7,
rsp->data + x,
rsp->data_len - x - 1);
rsp->data[x - 8] -= 8;
rsp->data_len -= 8;
entry->rq_seq = rsp->data[x - 3] >> 2;
if (!entry->bridging_level)
entry->req.msg.cmd = entry->req.msg.target_cmd;
continue;
}
} else {
//x += sizeof(rsp->payload.ipmi_response);
if (rsp->data[x-1] != 0)
lprintf(LOG_DEBUG, "WARNING: Bridged "
"cmd ccode = 0x%02x",
rsp->data[x-1]);
}
}
ipmi_req_remove_entry(rsp->payload.ipmi_response.rq_seq,
rsp->payload.ipmi_response.cmd);
} else {
lprintf(LOG_INFO, "IPMI Request Match NOT FOUND");
rsp = ipmi_lan_recv_packet(intf);
continue;
}
}
break;
}
/* shift response data to start of array */
if (rsp && rsp->data_len > x) {
rsp->data_len -= x;
if (rsp->session.payloadtype == IPMI_PAYLOAD_TYPE_IPMI)
rsp->data_len -= 1; /* We don't want the checksum */
memmove(rsp->data, rsp->data + x, rsp->data_len);
memset(rsp->data + rsp->data_len, 0, IPMI_BUF_SIZE - rsp->data_len);
}
return rsp;
}
/*
* IPMI LAN Request Message Format
* +--------------------+
* | rmcp.ver | 4 bytes
* | rmcp.__reserved |
* | rmcp.seq |
* | rmcp.class |
* +--------------------+
* | session.authtype | 9 bytes
* | session.seq |
* | session.id |
* +--------------------+
* | [session.authcode] | 16 bytes (AUTHTYPE != none)
* +--------------------+
* | message length | 1 byte
* +--------------------+
* | message.rs_addr | 6 bytes
* | message.netfn_lun |
* | message.checksum |
* | message.rq_addr |
* | message.rq_seq |
* | message.cmd |
* +--------------------+
* | [request data] | data_len bytes
* +--------------------+
* | checksum | 1 byte
* +--------------------+
*/
static struct ipmi_rq_entry *
ipmi_lan_build_cmd(struct ipmi_intf * intf, struct ipmi_rq * req, int isRetry)
{
struct rmcp_hdr rmcp = {
.ver = RMCP_VERSION_1,
.class = RMCP_CLASS_IPMI,
.seq = 0xff,
};
uint8_t * msg, * temp;
int cs, mp, tmp;
int ap = 0;
int len = 0;
int cs2 = 0, cs3 = 0;
struct ipmi_rq_entry * entry;
struct ipmi_session * s = intf->session;
static int curr_seq = 0;
uint8_t our_address = intf->my_addr;
if (our_address == 0)
our_address = IPMI_BMC_SLAVE_ADDR;
if (isRetry == 0)
curr_seq++;
if (curr_seq >= 64)
curr_seq = 0;
// Bug in the existing code where it keeps on adding same command/seq pair
// in the lookup entry list.
// Check if we have cmd,seq pair already in our list. As we are not changing
// the seq number we have to re-use the node which has existing
// command and sequence number. If we add then we will have redundant node with
// same cmd,seq pair
entry = ipmi_req_lookup_entry(curr_seq, req->msg.cmd);
if (entry)
{
// This indicates that we have already same command and seq in list
// No need to add once again and we will re-use the existing node.
// Only thing we have to do is clear the msg_data as we create
// a new one below in the code for it.
if (entry->msg_data) {
free(entry->msg_data);
entry->msg_data = NULL;
}
}
else
{
// We dont have this request in the list so we can add it
// to the list
entry = ipmi_req_add_entry(intf, req, curr_seq);
if (entry == NULL)
return NULL;
}
len = req->msg.data_len + 29;
if (s->active && s->authtype)
len += 16;
if (intf->transit_addr != intf->my_addr && intf->transit_addr != 0)
len += 8;
msg = malloc(len);
if (msg == NULL) {
lprintf(LOG_ERR, "ipmitool: malloc failure");
return NULL;
}
memset(msg, 0, len);
/* rmcp header */
memcpy(msg, &rmcp, sizeof(rmcp));
len = sizeof(rmcp);
/* ipmi session header */
msg[len++] = s->active ? s->authtype : 0;
msg[len++] = s->in_seq & 0xff;
msg[len++] = (s->in_seq >> 8) & 0xff;
msg[len++] = (s->in_seq >> 16) & 0xff;
msg[len++] = (s->in_seq >> 24) & 0xff;
memcpy(msg+len, &s->session_id, 4);
len += 4;
/* ipmi session authcode */
if (s->active && s->authtype) {
ap = len;
memcpy(msg+len, s->authcode, 16);
len += 16;
}
/* message length */
if ((intf->target_addr == our_address) || !bridge_possible) {
entry->bridging_level = 0;
msg[len++] = req->msg.data_len + 7;
cs = mp = len;
} else {
/* bridged request: encapsulate w/in Send Message */
entry->bridging_level = 1;
msg[len++] = req->msg.data_len + 15 +
(intf->transit_addr != intf->my_addr && intf->transit_addr != 0 ? 8 : 0);
cs = mp = len;
msg[len++] = IPMI_BMC_SLAVE_ADDR;
msg[len++] = IPMI_NETFN_APP << 2;
tmp = len - cs;
msg[len++] = ipmi_csum(msg+cs, tmp);
cs2 = len;
msg[len++] = IPMI_REMOTE_SWID;
msg[len++] = curr_seq << 2;
msg[len++] = 0x34; /* Send Message rqst */
entry->req.msg.target_cmd = entry->req.msg.cmd; /* Save target command */
entry->req.msg.cmd = 0x34; /* (fixup request entry) */
if (intf->transit_addr == intf->my_addr || intf->transit_addr == 0) {
msg[len++] = (0x40|intf->target_channel); /* Track request*/
} else {
entry->bridging_level++;
msg[len++] = (0x40|intf->transit_channel); /* Track request*/
cs = len;
msg[len++] = intf->transit_addr;
msg[len++] = IPMI_NETFN_APP << 2;
tmp = len - cs;
msg[len++] = ipmi_csum(msg+cs, tmp);
cs3 = len;
msg[len++] = intf->my_addr;
msg[len++] = curr_seq << 2;
msg[len++] = 0x34; /* Send Message rqst */
msg[len++] = (0x40|intf->target_channel); /* Track request */
}
cs = len;
}
/* ipmi message header */
msg[len++] = intf->target_addr;
msg[len++] = req->msg.netfn << 2 | (req->msg.lun & 3);
tmp = len - cs;
msg[len++] = ipmi_csum(msg+cs, tmp);
cs = len;
if (!entry->bridging_level)
msg[len++] = IPMI_REMOTE_SWID;
/* Bridged message */
else if (entry->bridging_level)
msg[len++] = intf->my_addr;
entry->rq_seq = curr_seq;
msg[len++] = entry->rq_seq << 2;
msg[len++] = req->msg.cmd;
lprintf(LOG_DEBUG+1, ">> IPMI Request Session Header (level %d)", entry->bridging_level);
lprintf(LOG_DEBUG+1, ">> Authtype : %s",
val2str(s->authtype, ipmi_authtype_session_vals));
lprintf(LOG_DEBUG+1, ">> Sequence : 0x%08lx", (long)s->in_seq);
lprintf(LOG_DEBUG+1, ">> Session ID : 0x%08lx", (long)s->session_id);
lprintf(LOG_DEBUG+1, ">> IPMI Request Message Header");
lprintf(LOG_DEBUG+1, ">> Rs Addr : %02x", intf->target_addr);
lprintf(LOG_DEBUG+1, ">> NetFn : %02x", req->msg.netfn);
lprintf(LOG_DEBUG+1, ">> Rs LUN : %01x", 0);
lprintf(LOG_DEBUG+1, ">> Rq Addr : %02x", IPMI_REMOTE_SWID);
lprintf(LOG_DEBUG+1, ">> Rq Seq : %02x", entry->rq_seq);
lprintf(LOG_DEBUG+1, ">> Rq Lun : %01x", 0);
lprintf(LOG_DEBUG+1, ">> Command : %02x", req->msg.cmd);
/* message data */
if (req->msg.data_len) {
memcpy(msg+len, req->msg.data, req->msg.data_len);
len += req->msg.data_len;
}
/* second checksum */
tmp = len - cs;
msg[len++] = ipmi_csum(msg+cs, tmp);
/* bridged request: 2nd checksum */
if (entry->bridging_level) {
if (intf->transit_addr != intf->my_addr && intf->transit_addr != 0) {
tmp = len - cs3;
msg[len++] = ipmi_csum(msg+cs3, tmp);
}
tmp = len - cs2;
msg[len++] = ipmi_csum(msg+cs2, tmp);
}
if (s->active) {
/*
* s->authcode is already copied to msg+ap but some
* authtypes require portions of the ipmi message to
* create the authcode so they must be done last.
*/
switch (s->authtype) {
case IPMI_SESSION_AUTHTYPE_MD5:
temp = ipmi_auth_md5(s, msg+mp, msg[mp-1]);
memcpy(msg+ap, temp, 16);
break;
case IPMI_SESSION_AUTHTYPE_MD2:
temp = ipmi_auth_md2(s, msg+mp, msg[mp-1]);
memcpy(msg+ap, temp, 16);
break;
}
}
if (s->in_seq) {
s->in_seq++;
if (s->in_seq == 0)
s->in_seq++;
}
entry->msg_len = len;
entry->msg_data = msg;
return entry;
}
static struct ipmi_rs *
ipmi_lan_send_cmd(struct ipmi_intf * intf, struct ipmi_rq * req)
{
struct ipmi_rq_entry * entry;
struct ipmi_rs * rsp = NULL;
int try = 0;
int isRetry = 0;
lprintf(LOG_DEBUG, "ipmi_lan_send_cmd:opened=[%d], open=[%d]",
intf->opened, intf->open);
if (intf->opened == 0 && intf->open != NULL) {
if (intf->open(intf) < 0) {
lprintf(LOG_DEBUG, "Failed to open LAN interface");
return NULL;
}
lprintf(LOG_DEBUG, "\topened=[%d], open=[%d]",
intf->opened, intf->open);
}
for (;;) {
isRetry = ( try > 0 ) ? 1 : 0;
entry = ipmi_lan_build_cmd(intf, req, isRetry);
if (entry == NULL) {
lprintf(LOG_ERR, "Aborting send command, unable to build");
return NULL;
}
if (ipmi_lan_send_packet(intf, entry->msg_data, entry->msg_len) < 0) {
try++;
usleep(5000);
ipmi_req_remove_entry(entry->rq_seq, entry->req.msg.target_cmd);
continue;
}
/* if we are set to noanswer we do not expect response */
if (intf->noanswer)
break;
if (ipmi_oem_active(intf, "intelwv2"))
ipmi_lan_thump(intf);
usleep(100);
rsp = ipmi_lan_poll_recv(intf);
/* Duplicate Request ccode most likely indicates a response to
a previous retry. Ignore and keep polling. */
if((rsp != NULL) && (rsp->ccode == 0xcf)) {
rsp = NULL;
rsp = ipmi_lan_poll_recv(intf);
}
if (rsp)
break;
usleep(5000);
if (++try >= intf->session->retry) {
lprintf(LOG_DEBUG, " No response from remote controller");
break;
}
}
// We need to cleanup the existing entries from the list. Because if we
// keep it and then when we send the new command and if the response is for
// old command it still matches it and then returns success.
// This is the corner case where the remote controller responds very slowly.
//
// Example: We have to send command 23 and 2d.
// If we send command,seq as 23,10 and if we dont get any response it will
// retry 4 times with 23,10 and then come out here and indicate that there is no
// reponse from the remote controller and will send the next command for
// ie 2d,11. And if the BMC is slow to respond and returns 23,10 then it
// will match it in the list and will take response of command 23 as response
// for command 2d and return success. So ideally when retries are done and
// are out of this function we should be clearing the list to be safe so that
// we dont match the old response with new request.
// [23, 10] --> BMC
// [23, 10] --> BMC
// [23, 10] --> BMC
// [23, 10] --> BMC
// [2D, 11] --> BMC
// <-- [23, 10]
// here if we maintain 23,10 in the list then it will get matched and consider
// 23 response as response for 2D.
ipmi_req_clear_entries();
return rsp;
}
static uint8_t *
ipmi_lan_build_rsp(struct ipmi_intf * intf, struct ipmi_rs * rsp, int * llen)
{
struct rmcp_hdr rmcp = {
.ver = RMCP_VERSION_1,
.class = RMCP_CLASS_IPMI,
.seq = 0xff,
};
struct ipmi_session * s = intf->session;
int cs, mp, ap = 0, tmp;
int len;
uint8_t * msg;
len = rsp->data_len + 22;
if (s->active)
len += 16;
msg = malloc(len);
if (msg == NULL) {
lprintf(LOG_ERR, "ipmitool: malloc failure");
return NULL;
}
memset(msg, 0, len);
/* rmcp header */
memcpy(msg, &rmcp, 4);
len = sizeof(rmcp);
/* ipmi session header */
msg[len++] = s->active ? s->authtype : 0;
if (s->in_seq) {
s->in_seq++;
if (s->in_seq == 0)
s->in_seq++;
}
memcpy(msg+len, &s->in_seq, 4);
len += 4;
memcpy(msg+len, &s->session_id, 4);
len += 4;
/* session authcode, if session active and authtype is not none */
if (s->active && s->authtype) {
ap = len;
memcpy(msg+len, s->authcode, 16);
len += 16;
}
/* message length */
msg[len++] = rsp->data_len + 8;
/* message header */
cs = mp = len;
msg[len++] = IPMI_REMOTE_SWID;
msg[len++] = rsp->msg.netfn << 2;
tmp = len - cs;
msg[len++] = ipmi_csum(msg+cs, tmp);
cs = len;
msg[len++] = IPMI_BMC_SLAVE_ADDR;
msg[len++] = (rsp->msg.seq << 2) | (rsp->msg.lun & 3);
msg[len++] = rsp->msg.cmd;
/* completion code */
msg[len++] = rsp->ccode;
/* message data */
if (rsp->data_len) {
memcpy(msg+len, rsp->data, rsp->data_len);
len += rsp->data_len;
}
/* second checksum */
tmp = len - cs;
msg[len++] = ipmi_csum(msg+cs, tmp);
if (s->active) {
uint8_t * d;
switch (s->authtype) {
case IPMI_SESSION_AUTHTYPE_MD5:
d = ipmi_auth_md5(s, msg+mp, msg[mp-1]);
memcpy(msg+ap, d, 16);
break;
case IPMI_SESSION_AUTHTYPE_MD2:
d = ipmi_auth_md2(s, msg+mp, msg[mp-1]);
memcpy(msg+ap, d, 16);
break;
}
}
*llen = len;
return msg;
}
static int
ipmi_lan_send_rsp(struct ipmi_intf * intf, struct ipmi_rs * rsp)
{
uint8_t * msg;
int len = 0;
int rv;
msg = ipmi_lan_build_rsp(intf, rsp, &len);
if (len <= 0 || msg == NULL) {
lprintf(LOG_ERR, "Invalid response packet");
if (msg != NULL) {
free(msg);
msg = NULL;
}
return -1;
}
rv = sendto(intf->fd, msg, len, 0,
(struct sockaddr *)&intf->session->addr,
intf->session->addrlen);
if (rv < 0) {
lprintf(LOG_ERR, "Packet send failed");
if (msg != NULL) {
free(msg);
msg = NULL;
}
return -1;
}
if (msg != NULL) {
free(msg);
msg = NULL;
}
return 0;
}
/*
* IPMI SOL Payload Format
* +--------------------+
* | rmcp.ver | 4 bytes
* | rmcp.__reserved |
* | rmcp.seq |
* | rmcp.class |
* +--------------------+
* | session.authtype | 9 bytes
* | session.seq |
* | session.id |
* +--------------------+
* | message length | 1 byte
* +--------------------+
* | sol.seq | 5 bytes
* | sol.ack_seq |
* | sol.acc_count |
* | sol.control |
* | sol.__reserved |
* +--------------------+
* | [request data] | data_len bytes
* +--------------------+
*/
uint8_t * ipmi_lan_build_sol_msg(struct ipmi_intf * intf,
struct ipmi_v2_payload * payload,
int * llen)
{
struct rmcp_hdr rmcp = {
.ver = RMCP_VERSION_1,
.class = RMCP_CLASS_IPMI,
.seq = 0xff,
};
struct ipmi_session * session = intf->session;
/* msg will hold the entire message to be sent */
uint8_t * msg;
int len = 0;
len = sizeof(rmcp) + // RMCP Header (4)
10 + // IPMI Session Header
5 + // SOL header
payload->payload.sol_packet.character_count; // The actual payload
msg = malloc(len);
if (msg == NULL) {
lprintf(LOG_ERR, "ipmitool: malloc failure");
return NULL;
}
memset(msg, 0, len);
/* rmcp header */
memcpy(msg, &rmcp, sizeof(rmcp));
len = sizeof(rmcp);
/* ipmi session header */
msg[len++] = 0; /* SOL is always authtype = NONE */
msg[len++] = session->in_seq & 0xff;
msg[len++] = (session->in_seq >> 8) & 0xff;
msg[len++] = (session->in_seq >> 16) & 0xff;
msg[len++] = (session->in_seq >> 24) & 0xff;
msg[len++] = session->session_id & 0xff;
msg[len++] = (session->session_id >> 8) & 0xff;
msg[len++] = (session->session_id >> 16) & 0xff;
msg[len++] = ((session->session_id >> 24) + 0x10) & 0xff; /* Add 0x10 to MSB for SOL */
msg[len++] = payload->payload.sol_packet.character_count + 5;
/* sol header */
msg[len++] = payload->payload.sol_packet.packet_sequence_number;
msg[len++] = payload->payload.sol_packet.acked_packet_number;
msg[len++] = payload->payload.sol_packet.accepted_character_count;
msg[len] = payload->payload.sol_packet.is_nack ? 0x40 : 0;
msg[len] |= payload->payload.sol_packet.assert_ring_wor ? 0x20 : 0;
msg[len] |= payload->payload.sol_packet.generate_break ? 0x10 : 0;
msg[len] |= payload->payload.sol_packet.deassert_cts ? 0x08 : 0;
msg[len] |= payload->payload.sol_packet.deassert_dcd_dsr ? 0x04 : 0;
msg[len] |= payload->payload.sol_packet.flush_inbound ? 0x02 : 0;
msg[len++] |= payload->payload.sol_packet.flush_outbound ? 0x01 : 0;
len++; /* On SOL there's and additional fifth byte before the data starts */
if (payload->payload.sol_packet.character_count) {
/* We may have data to add */
memcpy(msg + len,
payload->payload.sol_packet.data,
payload->payload.sol_packet.character_count);
len += payload->payload.sol_packet.character_count;
}
session->in_seq++;
if (session->in_seq == 0)
session->in_seq++;
*llen = len;
return msg;
}
/*
* is_sol_packet
*/
static int
is_sol_packet(struct ipmi_rs * rsp)
{
return (rsp &&
(rsp->session.payloadtype == IPMI_PAYLOAD_TYPE_SOL));
}
/*
* sol_response_acks_packet
*/
static int
sol_response_acks_packet(struct ipmi_rs * rsp,
struct ipmi_v2_payload * payload)
{
return (is_sol_packet(rsp) &&
payload &&
(payload->payload_type == IPMI_PAYLOAD_TYPE_SOL) &&
(rsp->payload.sol_packet.acked_packet_number ==
payload->payload.sol_packet.packet_sequence_number));
}
/*
* ipmi_lan_send_sol_payload
*
*/
static struct ipmi_rs *
ipmi_lan_send_sol_payload(struct ipmi_intf * intf,
struct ipmi_v2_payload * payload)
{
struct ipmi_rs * rsp = NULL;
uint8_t * msg;
int len;
int try = 0;
if (intf->opened == 0 && intf->open != NULL) {
if (intf->open(intf) < 0)
return NULL;
}
msg = ipmi_lan_build_sol_msg(intf, payload, &len);
if (len <= 0 || msg == NULL) {
lprintf(LOG_ERR, "Invalid SOL payload packet");
if (msg != NULL) {
free(msg);
msg = NULL;
}
return NULL;
}
lprintf(LOG_DEBUG, ">> SENDING A SOL MESSAGE\n");
for (;;) {
if (ipmi_lan_send_packet(intf, msg, len) < 0) {
try++;
usleep(5000);
continue;
}
/* if we are set to noanswer we do not expect response */
if (intf->noanswer)
break;
if (payload->payload.sol_packet.packet_sequence_number == 0) {
/* We're just sending an ACK. No need to retry. */
break;
}
usleep(100);
rsp = ipmi_lan_recv_sol(intf); /* Grab the next packet */
if (sol_response_acks_packet(rsp, payload))
break;
else if (is_sol_packet(rsp) && rsp->data_len)
{
/*
* We're still waiting for our ACK, but we more data from
* the BMC
*/
intf->session->sol_data.sol_input_handler(rsp);
}
usleep(5000);
if (++try >= intf->session->retry) {
lprintf(LOG_DEBUG, " No response from remote controller");
break;
}
}
if (msg != NULL) {
free(msg);
msg = NULL;
}
return rsp;
}
/*
* is_sol_partial_ack
*
* Determine if the response is a partial ACK/NACK that indicates
* we need to resend part of our packet.
*
* returns the number of characters we need to resend, or
* 0 if this isn't an ACK or we don't need to resend anything
*/
static int is_sol_partial_ack(struct ipmi_v2_payload * v2_payload,
struct ipmi_rs * rsp)
{
int chars_to_resend = 0;
if (v2_payload &&
rsp &&
is_sol_packet(rsp) &&
sol_response_acks_packet(rsp, v2_payload) &&
(rsp->payload.sol_packet.accepted_character_count <
v2_payload->payload.sol_packet.character_count))
{
if (rsp->payload.sol_packet.accepted_character_count == 0) {
/* We should not resend data */
chars_to_resend = 0;
}
else
{
chars_to_resend =
v2_payload->payload.sol_packet.character_count -
rsp->payload.sol_packet.accepted_character_count;
}
}
return chars_to_resend;
}
/*
* set_sol_packet_sequence_number
*/
static void set_sol_packet_sequence_number(struct ipmi_intf * intf,
struct ipmi_v2_payload * v2_payload)
{
/* Keep our sequence number sane */
if (intf->session->sol_data.sequence_number > 0x0F)
intf->session->sol_data.sequence_number = 1;
v2_payload->payload.sol_packet.packet_sequence_number =
intf->session->sol_data.sequence_number++;
}
/*
* ipmi_lan_send_sol
*
* Sends a SOL packet.. We handle partial ACK/NACKs from the BMC here.
*
* Returns a pointer to the SOL ACK we received, or
* 0 on failure
*
*/
struct ipmi_rs *
ipmi_lan_send_sol(struct ipmi_intf * intf,
struct ipmi_v2_payload * v2_payload)
{
struct ipmi_rs * rsp;
int chars_to_resend = 0;
v2_payload->payload_type = IPMI_PAYLOAD_TYPE_SOL;
/*
* Payload length is just the length of the character
* data here.
*/
v2_payload->payload.sol_packet.acked_packet_number = 0; /* NA */
set_sol_packet_sequence_number(intf, v2_payload);
v2_payload->payload.sol_packet.accepted_character_count = 0; /* NA */
rsp = ipmi_lan_send_sol_payload(intf, v2_payload);
/* Determine if we need to resend some of our data */
chars_to_resend = is_sol_partial_ack(v2_payload, rsp);
while (chars_to_resend)
{
/*
* We first need to handle any new data we might have
* received in our NACK
*/
if (rsp->data_len)
intf->session->sol_data.sol_input_handler(rsp);
set_sol_packet_sequence_number(intf, v2_payload);
/* Just send the required data */
memmove(v2_payload->payload.sol_packet.data,
v2_payload->payload.sol_packet.data +
rsp->payload.sol_packet.accepted_character_count,
chars_to_resend);
v2_payload->payload.sol_packet.character_count = chars_to_resend;
rsp = ipmi_lan_send_sol_payload(intf, v2_payload);
chars_to_resend = is_sol_partial_ack(v2_payload, rsp);
}
return rsp;
}
/*
* check_sol_packet_for_new_data
*
* Determine whether the SOL packet has already been seen
* and whether the packet has new data for us.
*
* This function has the side effect of removing an previously
* seen data, and moving new data to the front.
*
* It also "Remembers" the data so we don't get repeats.
*
*/
static int
check_sol_packet_for_new_data(struct ipmi_intf * intf,
struct ipmi_rs *rsp)
{
static uint8_t last_received_sequence_number = 0;
static uint8_t last_received_byte_count = 0;
int new_data_size = 0;
if (rsp &&
(rsp->session.payloadtype == IPMI_PAYLOAD_TYPE_SOL))
{
uint8_t unaltered_data_len = rsp->data_len;
if (rsp->payload.sol_packet.packet_sequence_number ==
last_received_sequence_number)
{
/*
* This is the same as the last packet, but may include
* extra data
*/
new_data_size = rsp->data_len - last_received_byte_count;
if (new_data_size > 0)
{
/* We have more data to process */
memmove(rsp->data,
rsp->data +
rsp->data_len - new_data_size,
new_data_size);
}
rsp->data_len = new_data_size;
}
/*
*Rember the data for next round
*/
if (rsp && rsp->payload.sol_packet.packet_sequence_number)
{
last_received_sequence_number =
rsp->payload.sol_packet.packet_sequence_number;
last_received_byte_count = unaltered_data_len;
}
}
return new_data_size;
}
/*
* ack_sol_packet
*
* Provided the specified packet looks reasonable, ACK it.
*/
static void
ack_sol_packet(struct ipmi_intf * intf,
struct ipmi_rs * rsp)
{
if (rsp &&
(rsp->session.payloadtype == IPMI_PAYLOAD_TYPE_SOL) &&
(rsp->payload.sol_packet.packet_sequence_number))
{
struct ipmi_v2_payload ack;
memset(&ack, 0, sizeof(struct ipmi_v2_payload));
ack.payload_type = IPMI_PAYLOAD_TYPE_SOL;
/*
* Payload length is just the length of the character
* data here.
*/
ack.payload_length = 0;
/* ACK packets have sequence numbers of 0 */
ack.payload.sol_packet.packet_sequence_number = 0;
ack.payload.sol_packet.acked_packet_number =
rsp->payload.sol_packet.packet_sequence_number;
ack.payload.sol_packet.accepted_character_count = rsp->data_len;
ipmi_lan_send_sol_payload(intf, &ack);
}
}
/*
* ipmi_recv_sol
*
* Receive a SOL packet and send an ACK in response.
*
*/
static struct ipmi_rs *
ipmi_lan_recv_sol(struct ipmi_intf * intf)
{
struct ipmi_rs * rsp = ipmi_lan_poll_recv(intf);
ack_sol_packet(intf, rsp);
/*
* Remembers the data sent, and alters the data to just
* include the new stuff.
*/
check_sol_packet_for_new_data(intf, rsp);
return rsp;
}
/* send a get device id command to keep session active */
static int
ipmi_lan_keepalive(struct ipmi_intf * intf)
{
struct ipmi_rs * rsp;
struct ipmi_rq req = { msg: {
netfn: IPMI_NETFN_APP,
cmd: 1,
}};
if (!intf->opened)
return 0;
rsp = intf->sendrecv(intf, &req);
if (rsp == NULL)
return -1;
if (rsp->ccode > 0)
return -1;
return 0;
}
/*
* IPMI Get Channel Authentication Capabilities Command
*/
static int
ipmi_get_auth_capabilities_cmd(struct ipmi_intf * intf)
{
struct ipmi_rs * rsp;
struct ipmi_rq req;
struct ipmi_session * s = intf->session;
uint8_t msg_data[2];
msg_data[0] = IPMI_LAN_CHANNEL_E;
msg_data[1] = s->privlvl;
memset(&req, 0, sizeof(req));
req.msg.netfn = IPMI_NETFN_APP;
req.msg.cmd = 0x38;
req.msg.data = msg_data;
req.msg.data_len = 2;
rsp = intf->sendrecv(intf, &req);
if (rsp == NULL) {
lprintf(LOG_INFO, "Get Auth Capabilities command failed");
return -1;
}
if (verbose > 2)
printbuf(rsp->data, rsp->data_len, "get_auth_capabilities");
if (rsp->ccode > 0) {
lprintf(LOG_INFO, "Get Auth Capabilities command failed: %s",
val2str(rsp->ccode, completion_code_vals));
return -1;
}
lprintf(LOG_DEBUG, "Channel %02x Authentication Capabilities:",
rsp->data[0]);
lprintf(LOG_DEBUG, " Privilege Level : %s",
val2str(req.msg.data[1], ipmi_privlvl_vals));
lprintf(LOG_DEBUG, " Auth Types : %s%s%s%s%s",
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_NONE) ? "NONE " : "",
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_MD2) ? "MD2 " : "",
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_MD5) ? "MD5 " : "",
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_PASSWORD) ? "PASSWORD " : "",
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_OEM) ? "OEM " : "");
lprintf(LOG_DEBUG, " Per-msg auth : %sabled",
(rsp->data[2] & IPMI_AUTHSTATUS_PER_MSG_DISABLED) ?
"dis" : "en");
lprintf(LOG_DEBUG, " User level auth : %sabled",
(rsp->data[2] & IPMI_AUTHSTATUS_PER_USER_DISABLED) ?
"dis" : "en");
lprintf(LOG_DEBUG, " Non-null users : %sabled",
(rsp->data[2] & IPMI_AUTHSTATUS_NONNULL_USERS_ENABLED) ?
"en" : "dis");
lprintf(LOG_DEBUG, " Null users : %sabled",
(rsp->data[2] & IPMI_AUTHSTATUS_NULL_USERS_ENABLED) ?
"en" : "dis");
lprintf(LOG_DEBUG, " Anonymous login : %sabled",
(rsp->data[2] & IPMI_AUTHSTATUS_ANONYMOUS_USERS_ENABLED) ?
"en" : "dis");
lprintf(LOG_DEBUG, "");
s->authstatus = rsp->data[2];
if (s->password &&
(s->authtype_set == 0 ||
s->authtype_set == IPMI_SESSION_AUTHTYPE_MD5) &&
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_MD5))
{
s->authtype = IPMI_SESSION_AUTHTYPE_MD5;
}
else if (s->password &&
(s->authtype_set == 0 ||
s->authtype_set == IPMI_SESSION_AUTHTYPE_MD2) &&
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_MD2))
{
s->authtype = IPMI_SESSION_AUTHTYPE_MD2;
}
else if (s->password &&
(s->authtype_set == 0 ||
s->authtype_set == IPMI_SESSION_AUTHTYPE_PASSWORD) &&
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_PASSWORD))
{
s->authtype = IPMI_SESSION_AUTHTYPE_PASSWORD;
}
else if (s->password &&
(s->authtype_set == 0 ||
s->authtype_set == IPMI_SESSION_AUTHTYPE_OEM) &&
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_OEM))
{
s->authtype = IPMI_SESSION_AUTHTYPE_OEM;
}
else if ((s->authtype_set == 0 ||
s->authtype_set == IPMI_SESSION_AUTHTYPE_NONE) &&
(rsp->data[1] & 1<<IPMI_SESSION_AUTHTYPE_NONE))
{
s->authtype = IPMI_SESSION_AUTHTYPE_NONE;
}
else {
if (!(rsp->data[1] & 1<<s->authtype_set))
lprintf(LOG_ERR, "Authentication type %s not supported",
val2str(s->authtype_set, ipmi_authtype_session_vals));
else
lprintf(LOG_ERR, "No supported authtypes found");
return -1;
}
lprintf(LOG_DEBUG, "Proceeding with AuthType %s",
val2str(s->authtype, ipmi_authtype_session_vals));
return 0;
}
/*
* IPMI Get Session Challenge Command
* returns a temporary session ID and 16 byte challenge string
*/
static int
ipmi_get_session_challenge_cmd(struct ipmi_intf * intf)
{
struct ipmi_rs * rsp;
struct ipmi_rq req;
struct ipmi_session * s = intf->session;
uint8_t msg_data[17];
memset(msg_data, 0, 17);
msg_data[0] = s->authtype;
memcpy(msg_data+1, s->username, 16);
memset(&req, 0, sizeof(req));
req.msg.netfn = IPMI_NETFN_APP;
req.msg.cmd = 0x39;
req.msg.data = msg_data;
req.msg.data_len = 17; /* 1 byte for authtype, 16 for user */
rsp = intf->sendrecv(intf, &req);
if (rsp == NULL) {
lprintf(LOG_ERR, "Get Session Challenge command failed");
return -1;
}
if (verbose > 2)
printbuf(rsp->data, rsp->data_len, "get_session_challenge");
if (rsp->ccode > 0) {
switch (rsp->ccode) {
case 0x81:
lprintf(LOG_ERR, "Invalid user name");
break;
case 0x82:
lprintf(LOG_ERR, "NULL user name not enabled");
break;
default:
lprintf(LOG_ERR, "Get Session Challenge command failed: %s",
val2str(rsp->ccode, completion_code_vals));
}
return -1;
}
memcpy(&s->session_id, rsp->data, 4);
memcpy(s->challenge, rsp->data + 4, 16);
lprintf(LOG_DEBUG, "Opening Session");
lprintf(LOG_DEBUG, " Session ID : %08lx", (long)s->session_id);
lprintf(LOG_DEBUG, " Challenge : %s", buf2str(s->challenge, 16));
return 0;
}
/*
* IPMI Activate Session Command
*/
static int
ipmi_activate_session_cmd(struct ipmi_intf * intf)
{
struct ipmi_rs * rsp;
struct ipmi_rq req;
struct ipmi_session * s = intf->session;
uint8_t msg_data[22];
memset(&req, 0, sizeof(req));
req.msg.netfn = IPMI_NETFN_APP;
req.msg.cmd = 0x3a;
msg_data[0] = s->authtype;
msg_data[1] = s->privlvl;
/* supermicro oem authentication hack */
if (ipmi_oem_active(intf, "supermicro")) {
uint8_t * special = ipmi_auth_special(s);
memcpy(s->authcode, special, 16);
memset(msg_data + 2, 0, 16);
lprintf(LOG_DEBUG, " OEM Auth : %s",
buf2str(special, 16));
} else {
memcpy(msg_data + 2, s->challenge, 16);
}
/* setup initial outbound sequence number */
get_random(msg_data+18, 4);
req.msg.data = msg_data;
req.msg.data_len = 22;
s->active = 1;
lprintf(LOG_DEBUG, " Privilege Level : %s",
val2str(msg_data[1], ipmi_privlvl_vals));
lprintf(LOG_DEBUG, " Auth Type : %s",
val2str(s->authtype, ipmi_authtype_session_vals));
rsp = intf->sendrecv(intf, &req);
if (rsp == NULL) {
lprintf(LOG_ERR, "Activate Session command failed");
s->active = 0;
return -1;
}
if (verbose > 2)
printbuf(rsp->data, rsp->data_len, "activate_session");
if (rsp->ccode) {
fprintf(stderr, "Activate Session error:");
switch (rsp->ccode) {
case 0x81:
lprintf(LOG_ERR, "\tNo session slot available");
break;
case 0x82:
lprintf(LOG_ERR, "\tNo slot available for given user - "
"limit reached");
break;
case 0x83:
lprintf(LOG_ERR, "\tNo slot available to support user "
"due to maximum privilege capacity");
break;
case 0x84:
lprintf(LOG_ERR, "\tSession sequence out of range");
break;
case 0x85:
lprintf(LOG_ERR, "\tInvalid session ID in request");
break;
case 0x86:
lprintf(LOG_ERR, "\tRequested privilege level "
"exceeds limit");
break;
case 0xd4:
lprintf(LOG_ERR, "\tInsufficient privilege level");
break;
default:
lprintf(LOG_ERR, "\t%s",
val2str(rsp->ccode, completion_code_vals));
}
return -1;
}
memcpy(&s->session_id, rsp->data + 1, 4);
s->in_seq = rsp->data[8] << 24 | rsp->data[7] << 16 | rsp->data[6] << 8 | rsp->data[5];
if (s->in_seq == 0)
++s->in_seq;
if (s->authstatus & IPMI_AUTHSTATUS_PER_MSG_DISABLED)
s->authtype = IPMI_SESSION_AUTHTYPE_NONE;
else if (s->authtype != (rsp->data[0] & 0xf)) {
lprintf(LOG_ERR, "Invalid Session AuthType %s in response",
val2str(s->authtype, ipmi_authtype_session_vals));
return -1;
}
bridge_possible = 1;
lprintf(LOG_DEBUG, "\nSession Activated");
lprintf(LOG_DEBUG, " Auth Type : %s",
val2str(rsp->data[0], ipmi_authtype_session_vals));
lprintf(LOG_DEBUG, " Max Priv Level : %s",
val2str(rsp->data[9], ipmi_privlvl_vals));
lprintf(LOG_DEBUG, " Session ID : %08lx", (long)s->session_id);
lprintf(LOG_DEBUG, " Inbound Seq : %08lx\n", (long)s->in_seq);
return 0;
}
/*
* IPMI Set Session Privilege Level Command
*/
static int
ipmi_set_session_privlvl_cmd(struct ipmi_intf * intf)
{
struct ipmi_rs * rsp;
struct ipmi_rq req;
uint8_t privlvl = intf->session->privlvl;
uint8_t backup_bridge_possible = bridge_possible;
if (privlvl <= IPMI_SESSION_PRIV_USER)
return 0; /* no need to set higher */
memset(&req, 0, sizeof(req));
req.msg.netfn = IPMI_NETFN_APP;
req.msg.cmd = 0x3b;
req.msg.data = &privlvl;
req.msg.data_len = 1;
bridge_possible = 0;
rsp = intf->sendrecv(intf, &req);
bridge_possible = backup_bridge_possible;
if (rsp == NULL) {
lprintf(LOG_ERR, "Set Session Privilege Level to %s failed",
val2str(privlvl, ipmi_privlvl_vals));
return -1;
}
if (verbose > 2)
printbuf(rsp->data, rsp->data_len, "set_session_privlvl");
if (rsp->ccode > 0) {
lprintf(LOG_ERR, "Set Session Privilege Level to %s failed: %s",
val2str(privlvl, ipmi_privlvl_vals),
val2str(rsp->ccode, completion_code_vals));
return -1;
}
lprintf(LOG_DEBUG, "Set Session Privilege Level to %s\n",
val2str(rsp->data[0], ipmi_privlvl_vals));
return 0;
}
static int
ipmi_close_session_cmd(struct ipmi_intf * intf)
{
struct ipmi_rs * rsp;
struct ipmi_rq req;
uint8_t msg_data[4];
uint32_t session_id = intf->session->session_id;
if (intf->session->active == 0)
return -1;
intf->target_addr = IPMI_BMC_SLAVE_ADDR;
bridge_possible = 0; /* Not a bridge message */
memcpy(&msg_data, &session_id, 4);
memset(&req, 0, sizeof(req));
req.msg.netfn = IPMI_NETFN_APP;
req.msg.cmd = 0x3c;
req.msg.data = msg_data;
req.msg.data_len = 4;
rsp = intf->sendrecv(intf, &req);
if (rsp == NULL) {
lprintf(LOG_ERR, "Close Session command failed");
return -1;
}
if (verbose > 2)
printbuf(rsp->data, rsp->data_len, "close_session");
if (rsp->ccode == 0x87) {
lprintf(LOG_ERR, "Failed to Close Session: invalid "
"session ID %08lx", (long)session_id);
return -1;
}
if (rsp->ccode > 0) {
lprintf(LOG_ERR, "Close Session command failed: %s",
val2str(rsp->ccode, completion_code_vals));
return -1;
}
lprintf(LOG_DEBUG, "Closed Session %08lx\n", (long)session_id);
return 0;
}
/*
* IPMI LAN Session Activation (IPMI spec v1.5 section 12.9)
*
* 1. send "RMCP Presence Ping" message, response message will
* indicate whether the platform supports IPMI
* 2. send "Get Channel Authentication Capabilities" command
* with AUTHTYPE = none, response packet will contain information
* about supported challenge/response authentication types
* 3. send "Get Session Challenge" command with AUTHTYPE = none
* and indicate the authentication type in the message, response
* packet will contain challenge string and temporary session ID.
* 4. send "Activate Session" command, authenticated with AUTHTYPE
* sent in previous message. Also sends the initial value for
* the outbound sequence number for BMC.
* 5. BMC returns response confirming session activation and
* session ID for this session and initial inbound sequence.
*/
static int
ipmi_lan_activate_session(struct ipmi_intf * intf)
{
int rc;
/* don't fail on ping because its not always supported.
* Supermicro's IPMI LAN 1.5 cards don't tolerate pings.
*/
if (!ipmi_oem_active(intf, "supermicro"))
ipmi_lan_ping(intf);
/* Some particular Intel boards need special help
*/
if (ipmi_oem_active(intf, "intelwv2"))
ipmi_lan_thump_first(intf);
rc = ipmi_get_auth_capabilities_cmd(intf);
if (rc < 0) {
goto fail;
}
rc = ipmi_get_session_challenge_cmd(intf);
if (rc < 0)
goto fail;
rc = ipmi_activate_session_cmd(intf);
if (rc < 0)
goto fail;
intf->abort = 0;
rc = ipmi_set_session_privlvl_cmd(intf);
if (rc < 0)
goto fail;
return 0;
fail:
lprintf(LOG_ERR, "Error: Unable to establish LAN session");
return -1;
}
static void
ipmi_lan_close(struct ipmi_intf * intf)
{
if (intf->abort == 0)
ipmi_close_session_cmd(intf);
if (intf->fd >= 0)
close(intf->fd);
ipmi_req_clear_entries();
if (intf->session != NULL) {
free(intf->session);
intf->session = NULL;
}
intf->opened = 0;
intf->manufacturer_id = IPMI_OEM_UNKNOWN;
intf = NULL;
}
static int
ipmi_lan_open(struct ipmi_intf * intf)
{
int rc;
struct ipmi_session *s;
if (intf == NULL || intf->session == NULL)
return -1;
s = intf->session;
if (s->port == 0)
s->port = IPMI_LAN_PORT;
if (s->privlvl == 0)
s->privlvl = IPMI_SESSION_PRIV_ADMIN;
if (s->timeout == 0)
s->timeout = IPMI_LAN_TIMEOUT;
if (s->retry == 0)
s->retry = IPMI_LAN_RETRY;
if (s->hostname == NULL || strlen((const char *)s->hostname) == 0) {
lprintf(LOG_ERR, "No hostname specified!");
return -1;
}
intf->abort = 1;
intf->session->sol_data.sequence_number = 1;
if (ipmi_intf_socket_connect (intf) == -1) {
lprintf(LOG_ERR, "Could not open socket!");
return -1;
}
if (intf->fd < 0) {
lperror(LOG_ERR, "Connect to %s failed",
s->hostname);
intf->close(intf);
return -1;
}
intf->opened = 1;
/* try to open session */
rc = ipmi_lan_activate_session(intf);
if (rc < 0) {
intf->close(intf);
intf->opened = 0;
return -1;
}
intf->manufacturer_id = ipmi_get_oem(intf);
/* automatically detect interface request and response sizes */
hpm2_detect_max_payload_size(intf);
return intf->fd;
}
static int
ipmi_lan_setup(struct ipmi_intf * intf)
{
intf->session = malloc(sizeof(struct ipmi_session));
if (intf->session == NULL) {
lprintf(LOG_ERR, "ipmitool: malloc failure");
return -1;
}
memset(intf->session, 0, sizeof(struct ipmi_session));
/* setup default LAN maximum request and response sizes */
intf->max_request_data_size = IPMI_LAN_MAX_REQUEST_SIZE;
intf->max_response_data_size = IPMI_LAN_MAX_RESPONSE_SIZE;
return 0;
}
static void
ipmi_lan_set_max_rq_data_size(struct ipmi_intf * intf, uint16_t size)
{
if (size + 7 > 0xFF) {
size = 0xFF - 7;
}
intf->max_request_data_size = size;
}
static void
ipmi_lan_set_max_rp_data_size(struct ipmi_intf * intf, uint16_t size)
{
if (size + 8 > 0xFF) {
size = 0xFF - 8;
}
intf->max_response_data_size = size;
}