diff options
Diffstat (limited to 'src/plugins/lan/lan.c')
-rw-r--r-- | src/plugins/lan/lan.c | 2112 |
1 files changed, 2112 insertions, 0 deletions
diff --git a/src/plugins/lan/lan.c b/src/plugins/lan/lan.c new file mode 100644 index 0000000..fb1a633 --- /dev/null +++ b/src/plugins/lan/lan.c @@ -0,0 +1,2112 @@ +/* + * 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; +} |