diff --git a/pyghmi/ipmi/command.py b/pyghmi/ipmi/command.py index 40f3c1b9..5f43b264 100644 --- a/pyghmi/ipmi/command.py +++ b/pyghmi/ipmi/command.py @@ -1,7 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corporation -# Copyright 2015 Lenovo +# Copyright 2015-2017 Lenovo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,7 +23,11 @@ import pyghmi.exceptions as exc import pyghmi.ipmi.events as sel import pyghmi.ipmi.fru as fru from pyghmi.ipmi.oem.lookup import get_oem_handler -from pyghmi.ipmi.private import session +try: + from pyghmi.ipmi.private import session +except ImportError: + session = None +from pyghmi.ipmi.private import localsession import pyghmi.ipmi.private.util as pygutil import pyghmi.ipmi.sdr as sdr import socket @@ -103,15 +107,16 @@ class Command(object): callback_args parameter. However, callback_args can optionally be populated if desired. - :param bmc: hostname or ip address of the BMC - :param userid: username to use to connect - :param password: password to connect to the BMC + :param bmc: hostname or ip address of the BMC (default is local) + :param userid: username to use to connect (default to no user) + :param password: password to connect to the BMC (defaults to no password) :param onlogon: function to run when logon completes in an asynchronous fashion. This will result in a greenthread behavior. :param kg: Optional parameter to use if BMC has a particular Kg configured """ - def __init__(self, bmc, userid, password, port=623, onlogon=None, kg=None): + def __init__(self, bmc=None, userid=None, password=None, port=623, + onlogon=None, kg=None): # TODO(jbjohnso): accept tuples and lists of each parameter for mass # operations without pushing the async complexities up the stack self.onlogon = onlogon @@ -121,7 +126,9 @@ class Command(object): self._netchannel = None self._ipv6support = None self.certverify = None - if onlogon is not None: + if bmc is None: + self.ipmi_session = localsession.Session() + elif onlogon is not None: self.ipmi_session = session.Session(bmc=bmc, userid=userid, password=password, diff --git a/pyghmi/ipmi/oem/lenovo/imm.py b/pyghmi/ipmi/oem/lenovo/imm.py index 4309a7ff..6b282c12 100644 --- a/pyghmi/ipmi/oem/lenovo/imm.py +++ b/pyghmi/ipmi/oem/lenovo/imm.py @@ -16,7 +16,7 @@ from datetime import datetime import json -from pyghmi.ipmi.private.session import _monotonic_time +from pyghmi.ipmi.private.util import _monotonic_time import pyghmi.util.webclient as webclient import urllib import weakref diff --git a/pyghmi/ipmi/private/localsession.py b/pyghmi/ipmi/private/localsession.py new file mode 100644 index 00000000..f9a75f66 --- /dev/null +++ b/pyghmi/ipmi/private/localsession.py @@ -0,0 +1,135 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2017 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ctypes import addressof, c_int, c_long, c_short, c_ubyte, c_uint +from ctypes import cast, create_string_buffer, POINTER, pointer, sizeof +from ctypes import Structure +import fcntl +import pyghmi.ipmi.private.util as iutil +from select import select + + +class IpmiMsg(Structure): + _fields_ = [('netfn', c_ubyte), + ('cmd', c_ubyte), + ('data_len', c_short), + ('data', POINTER(c_ubyte))] + + +class IpmiSystemInterfaceAddr(Structure): + _fields_ = [('addr_type', c_int), + ('channel', c_short), + ('lun', c_ubyte)] + + +class IpmiRecv(Structure): + _fields_ = [('recv_type', c_int), + ('addr', POINTER(IpmiSystemInterfaceAddr)), + ('addr_len', c_uint), + ('msgid', c_long), + ('msg', IpmiMsg)] + + +class IpmiReq(Structure): + _fields_ = [('addr', POINTER(IpmiSystemInterfaceAddr)), + ('addr_len', c_uint), + ('msgid', c_long), + ('msg', IpmiMsg)] + + +_IONONE = 0 +_IOWRITE = 1 +_IOREAD = 2 +IPMICTL_SET_MY_ADDRESS_CMD = _IOREAD << 30 | sizeof(c_uint) << 16 | \ + ord('i') << 8 | 17 # from ipmi.h +IPMICTL_SEND_COMMAND = _IOREAD << 30 | sizeof(IpmiReq) << 16 | \ + ord('i') << 8 | 13 # from ipmi.h +# next is really IPMICTL_RECEIVE_MSG_TRUNC, but will only use that +IPMICTL_RECV = (_IOWRITE | _IOREAD) << 30 | sizeof(IpmiRecv) << 16 | \ + ord('i') << 8 | 11 # from ipmi.h +BMC_SLAVE_ADDR = c_uint(0x20) +CURRCHAN = 0xf +ADDRTYPE = 0xc + + +class Session(object): + def __init__(self, devnode='/dev/ipmi0'): + """Create a local session inband + + :param: devnode: The path to the ipmi device + """ + self.ipmidev = open(devnode, 'rw') + fcntl.ioctl(self.ipmidev, IPMICTL_SET_MY_ADDRESS_CMD, BMC_SLAVE_ADDR) + # the interface is initted, create some reusable memory for our session + self.databuffer = create_string_buffer(4096) + self.req = IpmiReq() + self.rsp = IpmiRecv() + self.addr = IpmiSystemInterfaceAddr() + self.req.msg.data = cast(addressof(self.databuffer), POINTER(c_ubyte)) + self.rsp.msg.data = self.req.msg.data + self.userid = None + self.password = None + + def await_reply(self): + rd, _, _ = select((self.ipmidev,), (), (), 1) + while not rd: + rd, _, _ = select((self.ipmidev,), (), (), 1) + + @property + def parsed_rsp(self): + response = {'netfn': self.rsp.msg.netfn, 'command': self.rsp.msg.cmd, + 'code': ord(self.databuffer.raw[0]), + 'data': list(bytearray( + self.databuffer.raw[1:self.rsp.msg.data_len]))} + errorstr = iutil.get_ipmi_error(response) + if errorstr: + response['error'] = errorstr + return response + + def raw_command(self, + netfn, + command, + data=(), + bridge_request=None, + retry=True, + delay_xmit=None, + timeout=None, + waitall=False): + self.addr.channel = CURRCHAN + self.addr.addr_type = ADDRTYPE + self.req.addr_len = sizeof(IpmiSystemInterfaceAddr) + self.req.addr = pointer(self.addr) + self.req.msg.netfn = netfn + self.req.msg.cmd = command + data = buffer(bytearray(data)) + self.databuffer[:len(data)] = data[:len(data)] + self.req.msg.data_len = len(data) + fcntl.ioctl(self.ipmidev, IPMICTL_SEND_COMMAND, self.req) + self.await_reply() + self.rsp.msg.data_len = 4096 + self.rsp.addr = pointer(self.addr) + self.rsp.addr_len = sizeof(IpmiSystemInterfaceAddr) + fcntl.ioctl(self.ipmidev, IPMICTL_RECV, self.rsp) + return self.parsed_rsp + + +def main(): + a = Session('/dev/ipmi0') + print(repr(a.raw_command(0, 1))) + + +if __name__ == '__main__': + main() diff --git a/pyghmi/ipmi/private/session.py b/pyghmi/ipmi/private/session.py index 87d0ac1e..76c9e284 100644 --- a/pyghmi/ipmi/private/session.py +++ b/pyghmi/ipmi/private/session.py @@ -1,7 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corporation -# Copyright 2015-2016 Lenovo +# Copyright 2015-2017 Lenovo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ # This represents the low layer message framing portion of IPMI import collections -import ctypes import hashlib import hmac import operator @@ -32,6 +31,7 @@ from Crypto.Cipher import AES import pyghmi.exceptions as exc from pyghmi.ipmi.private import constants +from pyghmi.ipmi.private.util import get_ipmi_error, _monotonic_time try: dict.iteritems @@ -205,11 +205,6 @@ def _io_recvfrom(mysocket, size): except socket.error: return None -wintime = None -try: - wintime = ctypes.windll.kernel32.GetTickCount64 -except AttributeError: - pass try: IPPROTO_IPV6 = socket.IPPROTO_IPV6 @@ -219,20 +214,6 @@ except AttributeError: # targetting. -def _monotonic_time(): - """Provides a monotonic timer - - This code is concerned with relative, not absolute time. - This function facilitates that prior to python 3.3 - """ - # Python does not provide one until 3.3, so we make do - # for most OSes, os.times()[4] works well. - # for microsoft, GetTickCount64 - if wintime: - return wintime() / 1000.0 - return os.times()[4] - - def _poller(timeout=0): if sessionqueue: return True @@ -258,25 +239,6 @@ def _aespad(data): return newdata -def get_ipmi_error(response, suffix=""): - if 'error' in response: - return response['error'] + suffix - code = response['code'] - if code == 0: - return False - command = response['command'] - netfn = response['netfn'] - if ((netfn, command) in constants.command_completion_codes - and code in constants.command_completion_codes[(netfn, command)]): - res = constants.command_completion_codes[(netfn, command)][code] - res += suffix - elif code in constants.ipmi_completion_codes: - res = constants.ipmi_completion_codes[code] + suffix - else: - res = "Unknown code 0x%2x encountered" % code - return res - - def _checksum(*data): # Two's complement over the data csum = sum(data) csum ^= 0xff diff --git a/pyghmi/ipmi/private/util.py b/pyghmi/ipmi/private/util.py index cd7438fb..325bf214 100644 --- a/pyghmi/ipmi/private/util.py +++ b/pyghmi/ipmi/private/util.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2015 Lenovo +# Copyright 2015-2017 Lenovo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,10 +13,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import ctypes +import os import socket import struct +from pyghmi.ipmi.private import constants + try: range = xrange except NameError: @@ -27,6 +30,13 @@ except NameError: buffer = memoryview +wintime = None +try: + wintime = ctypes.windll.kernel32.GetTickCount64 +except AttributeError: + pass + + def decode_wireformat_uuid(rawguid): """Decode a wire format UUID @@ -64,3 +74,36 @@ def get_ipv4(hostname): addrinfo = socket.getaddrinfo(hostname, None, socket.AF_INET, socket.SOCK_STREAM) return [addrinfo[x][4][0] for x in range(len(addrinfo))] + + +def get_ipmi_error(response, suffix=""): + if 'error' in response: + return response['error'] + suffix + code = response['code'] + if code == 0: + return False + command = response['command'] + netfn = response['netfn'] + if ((netfn, command) in constants.command_completion_codes + and code in constants.command_completion_codes[(netfn, command)]): + res = constants.command_completion_codes[(netfn, command)][code] + res += suffix + elif code in constants.ipmi_completion_codes: + res = constants.ipmi_completion_codes[code] + suffix + else: + res = "Unknown code 0x%2x encountered" % code + return res + + +def _monotonic_time(): + """Provides a monotonic timer + + This code is concerned with relative, not absolute time. + This function facilitates that prior to python 3.3 + """ + # Python does not provide one until 3.3, so we make do + # for most OSes, os.times()[4] works well. + # for microsoft, GetTickCount64 + if wintime: + return wintime() / 1000.0 + return os.times()[4]