Mini Shell
import argparse
import logging
import os
import random
import socket
import threading
import yaml
from array import array
from collections import OrderedDict
import pyipmi
from pyipmi.logger import log
from pyipmi.interfaces import rmcp
from pyipmi.interfaces import ipmb
from pyipmi.msgs import (create_message, decode_message, encode_message,
create_response_message, create_request_by_name)
from pyipmi.msgs import constants
from pyipmi.session import Session
UDP_IP = "127.0.0.1"
UDP_PORT = 1623
sdr_list = OrderedDict()
handler_registry = {}
def register_message_handler(msg_name):
def reg(fn):
msg_type = type(create_request_by_name(msg_name))
handler_registry[msg_type] = fn
return fn
return reg
@register_message_handler("GetChannelAuthenticationCapabilities")
def handle_channel_auth_caps(context, req):
rsp = create_response_message(req)
rsp.support.straight = 1
rsp.status.anonymous_login_enabled = 1
return rsp
@register_message_handler("GetSessionChallenge")
def handle_get_session_challenge(context, req):
rsp = create_response_message(req)
rsp.temporary_session_id = random.randrange(1, 0xffffffff)
return rsp
@register_message_handler("ActivateSession")
def handle_activate_session(context, req):
session = context.session
rsp = create_response_message(req)
rsp.session_id = random.randrange(1, 0xffffffff)
rsp.authentication.type = req.authentication.type
rsp.privilege_level.maximum_allowed = req.privilege_level.maximum_requested
session.session_id = rsp.session_id
session.set_auth_type_user('admin', 'admin')
session.auth_type = Session.AUTH_TYPE_PASSWORD
return rsp
@register_message_handler("CloseSession")
def handle_close_session(context, req):
rsp = create_response_message(req)
return rsp
@register_message_handler("SetSessionPrivilegeLevel")
def handle_set_session_priv_level(context, req):
rsp = create_response_message(req)
return rsp
@register_message_handler("GetDeviceId")
def handle_get_device_id(context, req):
rsp = create_response_message(req)
return rsp
@register_message_handler("GetFruInventoryAreaInfo")
def handle_fru_inventory_are_info(context, req):
rsp = create_response_message(req)
cfg = context.config
try:
fru_filename = cfg['fru'][req.fru_id]
except KeyError:
log().warning('cannot find frufile for fru_id={} in config'.format(req.fru_id))
rsp.completion_code = constants.CC_PARAM_OUT_OF_RANGE
return rsp
except TypeError:
log().warning('cannot find frufile for fru_id={} in config'.format(req.fru_id))
rsp.completion_code = constants.CC_REQ_DATA_NOT_PRESENT
return rsp
try:
statinfo = os.stat(fru_filename)
rsp.area_size = statinfo.st_size
except FileNotFoundError:
log().warning('cannot open file={} for fru_id={}'.format(fru_filename, req.fru_id))
rsp.completion_code = constants.CC_PARAM_OUT_OF_RANGE
return rsp
return rsp
@register_message_handler("ReadFruData")
def handle_fru_read(context, req):
rsp = create_response_message(req)
cfg = context.config
try:
fru_filename = cfg['fru'][req.fru_id]
except KeyError:
rsp.completion_code = constants.CC_PARAM_OUT_OF_RANGE
log().debug('cannot find file for fru_id={} in config'.format(req.fru_id))
return rsp
try:
with open(fru_filename, 'rb') as fru:
fru.seek(req.offset)
d = fru.read(req.count)
rsp.count = len(d)
rsp.data = d
except FileNotFoundError:
log().debug('cannot open file={} for fru_id={}'.format(fru_filename, req.fru_id))
rsp.completion_code = constants.CC_PARAM_OUT_OF_RANGE
return rsp
return rsp
@register_message_handler("GetSdrRepositoryInfo")
def handle_sdr_repository_info(context, req):
rsp = create_response_message(req)
rsp.count = 0
return rsp
@register_message_handler("ReserveSdrRepository")
def handle_reserve_sdr_repositry(context, req):
rsp = create_response_message(req)
return rsp
@register_message_handler("ClearSdrRepository")
def handle_clear_sdr_repositry(context, req):
rsp = create_response_message(req)
rsp.status.erase_in_progress = constants.REPOSITORY_ERASURE_COMPLETED
return rsp
@register_message_handler("GetSdr")
def handle_get_sdr(context, req):
rsp = create_response_message(req)
if len(sdr_list) == 0:
log().warning('no SDR present')
rsp.completion_code = constants.CC_REQ_DATA_NOT_PRESENT
return rsp
next_index = list(sdr_list.keys()).index(req.record_id) + 1
try:
next_record_id = list(sdr_list)[next_index]
except IndexError:
next_record_id = 0xffff
rsp.next_record_id = next_record_id
sdr = sdr_list[req.record_id]
rsp.record_data = sdr.data[req.offset:req.offset+req.bytes_to_read]
return rsp
@register_message_handler("GetDeviceSdrInfo")
def handle_device_sdr_info(context, req):
rsp = create_response_message(req)
rsp.number_of_sensors = 0
return rsp
@register_message_handler("ReserveDeviceSdrRepository")
def handle_reserve_device_sdr_repository(context, req):
rsp = create_response_message(req)
return rsp
@register_message_handler("SendMessage")
def handle_send_message(context, req):
rsp = create_response_message(req)
rsp.completion_code = constants.CC_PARAM_OUT_OF_RANGE
return rsp
def handle_ipmi_request_msg(context, req):
try:
fct = handler_registry[type(req)]
except KeyError:
rsp = create_response_message(req)
log().warning('no handler for: {}'.format(type(req)))
rsp.completion_code = constants.CC_INV_CMD
return rsp
rsp = fct(context, req)
return rsp
def handle_rmcp_asf_msg(context, sdu):
asf = rmcp.AsfMsg()
asf.unpack(sdu)
# t = rmcp.AsfMsg().from_data(sdu)
if asf.asf_type == rmcp.AsfMsg.ASF_TYPE_PRESENCE_PING:
log().debug(f'ASF RX: ping: {asf}')
pong = rmcp.AsfPong()
pdu = pong.pack()
log().debug(f'ASF TX: pong: {asf}')
return pdu
def handle_rmcp_ipmi_msg(context, sdu):
def _get_group_id(ipmi_sdu):
group_id = None
if req_header.netfn == constants.NETFN_GROUP_EXTENSION:
group_id = ipmi_sdu[6]
return group_id
def _create_invalid_response(ipmi_sdu):
req_header = ipmb.IpmbHeaderReq(data=ipmi_sdu)
group_id = _get_group_id(ipmi_sdu)
log().warning('Cant create message: netfn 0x{:x} cmd: 0x{:x} group: {}'.format(req_header.netfn, req_header.cmdid, group_id))
log().debug('IPMI RX: {:s}'.format(
' '.join('%02x' % b for b in array('B', ipmi_sdu))))
# bytes are immutable ... so convert to change
a = bytearray(ipmi_sdu)
# set completion code . invalid command
a[6] = constants.CC_INV_CMD
# netfn + 1
a[1] = a[1] | 0x4
ipmi_sdu = bytes(a)
# rmcp ipmi rsp msg
ipmi_tx = rmcp.IpmiMsg(context.session)
pdu = ipmi_tx.pack(ipmi_sdu)
return pdu
session = context.session
session.sequence_number += 1
if session.sequence_number > 255:
session.sequence_number = 0
# rmcp ipmi req msg
ipmi_rx = rmcp.IpmiMsg()
ipmi_sdu = ipmi_rx.unpack(sdu)
req_header = ipmb.IpmbHeaderReq(data=ipmi_sdu)
group_id = _get_group_id(ipmi_sdu)
try:
req = create_message(req_header.netfn, req_header.cmdid, group_id)
except KeyError:
return _create_invalid_response(ipmi_sdu)
log().debug('IPMI RX: {}: {:s}'.format(req,
' '.join('%02x' % b for b in array('B', ipmi_sdu))))
decode_message(req, ipmi_sdu[6:-1])
rsp = handle_ipmi_request_msg(context, req)
data = encode_message(rsp)
rsp_header = ipmb.IpmbHeaderRsp()
rsp_header.from_req_header(req_header)
tx_data = ipmb.encode_ipmb_msg(rsp_header, data)
log().debug('IPMI TX: {}: {:s}'.format(rsp,
' '.join('%02x' % b for b in array('B', tx_data))))
# rmcp ipmi rsp msg
ipmi_tx = rmcp.IpmiMsg(context.session)
pdu = ipmi_tx.pack(tx_data)
if type(req) is pyipmi.msgs.device_messaging.CloseSessionReq:
session.session_id = None
session._auth_type = Session.AUTH_TYPE_NONE
session._auth_username = None
session._auth_password = None
context.state = ConnectionContext.STATE_CLOSED
return pdu
def load_sdr_dump(dump_file):
with open(dump_file, 'rb') as f:
while True:
h = f.read(5)
if not h:
break
t = pyipmi.sdr.SdrCommon(h)
b = f.read(t.length)
sdr = pyipmi.sdr.SdrCommon().from_data(h + b)
sdr_list[sdr.id] = sdr
class ConnectionContext():
STATE_IDLE = 0
STATE_ACTIVE = 1
STATE_CLOSED = 2
state = STATE_IDLE
def __init__(self, config, sock, addr):
self.config = config
self.sock = sock
self.addr = addr
self.session = Session()
def handle_thread(context, pdu):
msg = rmcp.RmcpMsg()
sdu = msg.unpack(pdu)
try:
handler = {
rmcp.RMCP_CLASS_ASF: handle_rmcp_asf_msg,
rmcp.RMCP_CLASS_IPMI: handle_rmcp_ipmi_msg,
}[msg.class_of_msg]
tx_data = handler(context, sdu)
rmcp_msg = rmcp.RmcpMsg(msg.class_of_msg)
pdu = rmcp_msg.pack(tx_data, context.session.sequence_number)
context.sock.sendto(pdu, context.addr)
except IndexError:
print('unknown class_of_msg {}'.format(msg.class_of_msg))
def main(args=None):
parser = argparse.ArgumentParser(description="IPMI server emulation.")
parser.add_argument("-p", "--port", type=int, dest="port", help="RMCP port", default=623)
parser.add_argument("-c", "--config", type=str, dest="config", help="Config file")
parser.add_argument(
"-v", action="store_true", dest="verbose", help="be more verbose"
)
args = parser.parse_args(args)
handler = logging.StreamHandler()
if args.verbose:
handler.setLevel(logging.DEBUG)
else:
handler.setLevel(logging.INFO)
pyipmi.logger.add_log_handler(handler)
pyipmi.logger.set_log_level(logging.DEBUG)
config = None
if args.config:
with open(args.config, 'r') as stream:
config = yaml.safe_load(stream)
if 'sdr' in config:
load_sdr_dump(config['sdr'])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, args.port))
connections = {}
while True:
pdu, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
# create new connection
if addr not in connections:
connections[addr] = ConnectionContext(config, sock, addr)
# remove for closed connections from dict
connections = {
key: value
for key, value in connections.items()
if value.state != value.STATE_CLOSED
}
thread = threading.Thread(target=handle_thread,
args=(connections[addr], pdu))
thread.daemon = True
thread.start()
if __name__ == '__main__':
main()
Zerion Mini Shell 1.0