mirror of
https://opendev.org/x/pyghmi
synced 2025-10-29 18:35:24 +00:00
Pyghmi does not follow the pep8 standard
Pep8 check is necessory for our code, it also makes your code easy to readable and understandable and beautiful. Change-Id: I4bb8434db07bf8fe82a4f65dfdfe7f2114b00083 Closes-Bug: #1699298
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pyghmi.version import version_info
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
@@ -25,7 +27,7 @@ sys.path.insert(0, os.path.abspath('.'))
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
@@ -38,7 +40,7 @@ templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
@@ -51,7 +53,7 @@ copyright = u'2013, Jarrod Johnson <jbjohnso@us.ibm.com>'
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
from pyghmi.version import version_info
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version_info.release_string()
|
||||
# The short X.Y version.
|
||||
@@ -59,37 +61,37 @@ version = version_info.version_string()
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
@@ -101,26 +103,26 @@ html_theme = 'default'
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
@@ -129,56 +131,55 @@ html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'pyghmidoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output -------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
# latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
# latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual])
|
||||
@@ -190,26 +191,26 @@ latex_documents = [
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
# latex_show_urls = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
# latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output -------------------------------------------
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'jjohnson2@lenovo.com'
|
||||
|
||||
import pyghmi.ipmi.command as ipmicommand
|
||||
import pyghmi.ipmi.private.serversession as serversession
|
||||
import pyghmi.ipmi.private.session as ipmisession
|
||||
import traceback
|
||||
|
||||
__author__ = 'jjohnson2@lenovo.com'
|
||||
|
||||
|
||||
class Bmc(serversession.IpmiServer):
|
||||
def cold_reset(self):
|
||||
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
|
||||
try:
|
||||
from pyghmi.ipmi.private import session
|
||||
except ImportError:
|
||||
@@ -42,7 +43,6 @@ try:
|
||||
except NameError:
|
||||
buffer = memoryview
|
||||
|
||||
|
||||
boot_devices = {
|
||||
'net': 4,
|
||||
'network': 4,
|
||||
@@ -90,7 +90,7 @@ def _mask_to_cidr(mask):
|
||||
|
||||
|
||||
def _cidr_to_mask(prefix):
|
||||
return struct.pack('>I', 2**prefix-1 << (32-prefix))
|
||||
return struct.pack('>I', 2 ** prefix - 1 << (32 - prefix))
|
||||
|
||||
|
||||
class Command(object):
|
||||
@@ -435,7 +435,7 @@ class Command(object):
|
||||
response = self.raw_command(netfn=0, command=1)
|
||||
if 'error' in response:
|
||||
raise exc.IpmiException(response['error'])
|
||||
assert(response['command'] == 1 and response['netfn'] == 1)
|
||||
assert (response['command'] == 1 and response['netfn'] == 1)
|
||||
powerstate = 'on' if (response['data'][0] & 1) else 'off'
|
||||
return {'powerstate': powerstate}
|
||||
|
||||
@@ -763,7 +763,7 @@ class Command(object):
|
||||
1: 'Static',
|
||||
2: 'DHCP',
|
||||
3: 'BIOS',
|
||||
4: 'Other',
|
||||
4: 'Other',
|
||||
}
|
||||
retdata['ipv4_configuration'] = v4cfgmethods[self._fetch_lancfg_param(
|
||||
channel, 4)]
|
||||
@@ -825,7 +825,7 @@ class Command(object):
|
||||
that
|
||||
"""
|
||||
if self._netchannel is None:
|
||||
for channel in chain((0xe, ), range(1, 0xc)):
|
||||
for channel in chain((0xe,), range(1, 0xc)):
|
||||
try:
|
||||
rsp = self.xraw_command(
|
||||
netfn=6, command=0x42, data=(channel,))
|
||||
@@ -1105,7 +1105,7 @@ class Command(object):
|
||||
return retstr
|
||||
|
||||
def _chunkwise_dcmi_set(self, command, data):
|
||||
chunks = [data[i:i+15] for i in range(0, len(data), 15)]
|
||||
chunks = [data[i:i + 15] for i in range(0, len(data), 15)]
|
||||
offset = 0
|
||||
for chunk in chunks:
|
||||
chunk = bytearray(chunk, 'utf-8')
|
||||
@@ -1184,7 +1184,7 @@ class Command(object):
|
||||
'dont_change': 0,
|
||||
'non_volatile': 1,
|
||||
'volatile': 2,
|
||||
#'reserved': 3
|
||||
# 'reserved': 3
|
||||
}
|
||||
b = 0
|
||||
b |= (access_update_modes[access_update_mode] << 6) & 0b11000000
|
||||
@@ -1207,7 +1207,7 @@ class Command(object):
|
||||
'dont_change': 0,
|
||||
'non_volatile': 1,
|
||||
'volatile': 2,
|
||||
#'reserved': 3
|
||||
# 'reserved': 3
|
||||
}
|
||||
b |= (privilege_update_modes[privilege_update_mode] << 6) & 0b11000000
|
||||
privilege_levels = {
|
||||
@@ -1292,7 +1292,7 @@ class Command(object):
|
||||
3: 'operator',
|
||||
4: 'administrator',
|
||||
5: 'proprietary',
|
||||
#0x0F: 'no_access'
|
||||
# 0x0F: 'no_access'
|
||||
}
|
||||
r['privilege_level'] = privilege_levels[data[1] & 0b00001111]
|
||||
return r
|
||||
@@ -1337,8 +1337,8 @@ class Command(object):
|
||||
0x0a: 'reserved for USB 1.x',
|
||||
0x0b: 'reserved for USB 2.x',
|
||||
0x0c: 'System Interface (KCS, SMIC, or BT)',
|
||||
## 60h-7Fh: OEM
|
||||
## all other reserved
|
||||
# 60h-7Fh: OEM
|
||||
# all other reserved
|
||||
}
|
||||
t = data[1] & 0b01111111
|
||||
if t in channel_medium_types:
|
||||
@@ -1450,7 +1450,7 @@ class Command(object):
|
||||
privilege_level: [reserved, callback, user,
|
||||
operatorm administrator, proprietary, no_access]
|
||||
"""
|
||||
## user access available during call-in or callback direct connection
|
||||
# user access available during call-in or callback direct connection
|
||||
if channel is None:
|
||||
channel = self.get_network_channel()
|
||||
data = [channel, uid]
|
||||
@@ -1636,7 +1636,7 @@ class Command(object):
|
||||
channel = self.get_network_channel()
|
||||
names = {}
|
||||
max_ids = self.get_channel_max_user_count(channel)
|
||||
for uid in range(1, max_ids+1):
|
||||
for uid in range(1, max_ids + 1):
|
||||
name = self.get_user_name(uid=uid)
|
||||
if name is not None:
|
||||
names[uid] = self.get_user(uid=uid, channel=channel)
|
||||
|
||||
@@ -38,7 +38,7 @@ class Console(object):
|
||||
:param kg: optional parameter for BMCs configured to require it
|
||||
"""
|
||||
|
||||
#TODO(jbjohnso): still need an exit and a data callin function
|
||||
# TODO(jbjohnso): still need an exit and a data callin function
|
||||
def __init__(self, bmc, userid, password,
|
||||
iohandler, port=623,
|
||||
force=False, kg=None):
|
||||
@@ -70,19 +70,19 @@ class Console(object):
|
||||
if 'error' in response:
|
||||
self._print_error(response['error'])
|
||||
return
|
||||
#Send activate sol payload directive
|
||||
#netfn= 6 (application)
|
||||
#command = 0x48 (activate payload)
|
||||
#data = (1, sol payload type
|
||||
# Send activate sol payload directive
|
||||
# netfn= 6 (application)
|
||||
# command = 0x48 (activate payload)
|
||||
# data = (1, sol payload type
|
||||
# 1, first instance
|
||||
# 0b11000000, -encrypt, authenticate,
|
||||
# disable serial/modem alerts, CTS fine
|
||||
# 0, 0, 0 reserved
|
||||
response = self.ipmi_session.raw_command(netfn=0x6, command=0x48,
|
||||
data=(1, 1, 192, 0, 0, 0))
|
||||
#given that these are specific to the command,
|
||||
#it's probably best if one can grep the error
|
||||
#here instead of in constants
|
||||
# given that these are specific to the command,
|
||||
# it's probably best if one can grep the error
|
||||
# here instead of in constants
|
||||
sol_activate_codes = {
|
||||
0x81: 'SOL is disabled',
|
||||
0x82: 'Maximum SOL session count reached',
|
||||
@@ -118,19 +118,19 @@ class Console(object):
|
||||
self._print_error(response['error'])
|
||||
return
|
||||
self.activated = True
|
||||
#data[0:3] is reserved except for the test mode, which we don't use
|
||||
# data[0:3] is reserved except for the test mode, which we don't use
|
||||
data = response['data']
|
||||
self.maxoutcount = (data[5] << 8) + data[4]
|
||||
#BMC tells us this is the maximum allowed size
|
||||
#data[6:7] is the promise of how small packets are going to be, but we
|
||||
#don't have any reason to worry about it
|
||||
# BMC tells us this is the maximum allowed size
|
||||
# data[6:7] is the promise of how small packets are going to be, but we
|
||||
# don't have any reason to worry about it
|
||||
if (data[8] + (data[9] << 8)) not in (623, 28418):
|
||||
#TODO(jbjohnso): support atypical SOL port number
|
||||
# TODO(jbjohnso): support atypical SOL port number
|
||||
raise NotImplementedError("Non-standard SOL Port Number")
|
||||
#ignore data[10:11] for now, the vlan detail, shouldn't matter to this
|
||||
#code anyway...
|
||||
#NOTE(jbjohnso):
|
||||
#We will use a special purpose keepalive
|
||||
# ignore data[10:11] for now, the vlan detail, shouldn't matter to this
|
||||
# code anyway...
|
||||
# NOTE(jbjohnso):
|
||||
# We will use a special purpose keepalive
|
||||
if self.ipmi_session.sol_handler is not None:
|
||||
# If there is erroneously another SOL handler already, notify
|
||||
# it of newly established session
|
||||
@@ -296,9 +296,9 @@ class Console(object):
|
||||
def _got_sol_payload(self, payload):
|
||||
"""SOL payload callback
|
||||
"""
|
||||
#TODO(jbjohnso) test cases to throw some likely scenarios at functions
|
||||
#for example, retry with new data, retry with no new data
|
||||
#retry with unexpected sequence number
|
||||
# TODO(jbjohnso) test cases to throw some likely scenarios at functions
|
||||
# for example, retry with new data, retry with no new data
|
||||
# retry with unexpected sequence number
|
||||
if type(payload) == dict: # we received an error condition
|
||||
self.activated = False
|
||||
self._print_error(payload)
|
||||
@@ -310,15 +310,16 @@ class Console(object):
|
||||
poweredoff = payload[3] & 0b100000
|
||||
deactivated = payload[3] & 0b10000
|
||||
breakdetected = payload[3] & 0b100
|
||||
#for now, ignore overrun. I assume partial NACK for this reason or for
|
||||
#no reason would be treated the same, new payload with partial data
|
||||
# for now, ignore overrun. I assume partial NACK for this reason or
|
||||
# for no reason would be treated the same, new payload with partial
|
||||
# data.
|
||||
remdata = ""
|
||||
remdatalen = 0
|
||||
if newseq != 0: # this packet at least has some data to send to us..
|
||||
if len(payload) > 4:
|
||||
remdatalen = len(payload[4:]) # store remote len before dupe
|
||||
#retry logic, we must ack *this* many even if it is
|
||||
#a retry packet with new partial data
|
||||
# retry logic, we must ack *this* many even if it is
|
||||
# a retry packet with new partial data
|
||||
remdata = struct.pack("%dB" % remdatalen, *payload[4:])
|
||||
if newseq == self.remseq: # it is a retry, but could have new data
|
||||
if remdatalen > self.lastsize:
|
||||
@@ -331,16 +332,16 @@ class Console(object):
|
||||
if remdata: # Do not subject callers to empty data
|
||||
self._print_data(remdata)
|
||||
ackpayload = (0, self.remseq, remdatalen, 0)
|
||||
#Why not put pending data into the ack? because it's rare
|
||||
#and might be hard to decide what to do in the context of
|
||||
#retry situation
|
||||
# Why not put pending data into the ack? because it's rare
|
||||
# and might be hard to decide what to do in the context of
|
||||
# retry situation
|
||||
try:
|
||||
self.send_payload(ackpayload, retry=False)
|
||||
except exc.IpmiException:
|
||||
#if the session is broken, then close the SOL session
|
||||
# if the session is broken, then close the SOL session
|
||||
self.close()
|
||||
if self.myseq != 0 and ackseq == self.myseq: # the bmc has something
|
||||
# to say about last xmit
|
||||
# to say about last xmit
|
||||
self.awaitingack = False
|
||||
if nacked and not breakdetected: # the BMC was in some way unhappy
|
||||
if poweredoff:
|
||||
@@ -379,9 +380,9 @@ class Console(object):
|
||||
to provide their own event loop behavior, though this could be used
|
||||
within the greenthread implementation of caller's choice if desired.
|
||||
"""
|
||||
#wait_for_rsp promises to return a false value when no sessions are
|
||||
#alive anymore
|
||||
#TODO(jbjohnso): wait_for_rsp is not returning a true value for our own
|
||||
#session
|
||||
# wait_for_rsp promises to return a false value when no sessions are
|
||||
# alive anymore
|
||||
# TODO(jbjohnso): wait_for_rsp is not returning a true value for our
|
||||
# own session
|
||||
while (1):
|
||||
session.Session.wait_for_rsp(timeout=600)
|
||||
|
||||
@@ -187,8 +187,8 @@ class FRU(object):
|
||||
frusubtype = self.sdr.fru_type_and_modifier & 0xff
|
||||
if frutype > 0x10 or frutype < 0x8 or frusubtype not in (0, 1, 2):
|
||||
return
|
||||
#TODO(jjohnson2): strict mode to detect pyghmi and BMC
|
||||
#gaps
|
||||
# TODO(jjohnson2): strict mode to detect pyghmi and BMC
|
||||
# gaps
|
||||
# raise iexc.PyghmiException(
|
||||
# 'Unsupported FRU device: {0:x}h, {1:x}h'.format(frutype,
|
||||
# frusubtype
|
||||
@@ -199,7 +199,7 @@ class FRU(object):
|
||||
return
|
||||
if self.databytes[0] != 1:
|
||||
return
|
||||
#TODO(jjohnson2): strict mode to flag potential BMC errors
|
||||
# TODO(jjohnson2): strict mode to flag potential BMC errors
|
||||
# raise iexc.BmcErrorException("Invalid/Unsupported FRU format")
|
||||
# Ignore the internal use even if present.
|
||||
self._parse_chassis()
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import pyghmi.constants as const
|
||||
|
||||
IPMI_BMC_ADDRESS = 0x20
|
||||
IPMI_SEND_MESSAGE_CMD = 0x34
|
||||
|
||||
import pyghmi.constants as const
|
||||
|
||||
|
||||
payload_types = {
|
||||
'ipmi': 0x0,
|
||||
|
||||
@@ -42,11 +42,11 @@ class ServerSession(ipmisession.Session):
|
||||
# role = request[1]
|
||||
self.clientsessionid = request[4:8]
|
||||
# TODO(jbjohnso): intelligently handle integrity/auth/conf
|
||||
#for now, forcibly do cipher suite 3
|
||||
# for now, forcibly do cipher suite 3
|
||||
self.managedsessionid = os.urandom(4)
|
||||
#table 13-17, 1 for now (hmac-sha1), 3 should also be supported
|
||||
#table 13-18, integrity, 1 for now is hmac-sha1-96, 4 is sha256
|
||||
#confidentiality: 1 is aes-cbc-128, the only one
|
||||
# table 13-17, 1 for now (hmac-sha1), 3 should also be supported
|
||||
# table 13-18, integrity, 1 for now is hmac-sha1-96, 4 is sha256
|
||||
# confidentiality: 1 is aes-cbc-128, the only one
|
||||
self.privlevel = 4
|
||||
response = (bytearray([clienttag, 0, self.privlevel, 0]) +
|
||||
self.clientsessionid + self.managedsessionid +
|
||||
@@ -97,7 +97,7 @@ class ServerSession(ipmisession.Session):
|
||||
self.maxpriv = self.rolem & 0b111
|
||||
namepresent = data[27]
|
||||
if namepresent == 0:
|
||||
#ignore null username for now
|
||||
# ignore null username for now
|
||||
return
|
||||
self.username = bytes(data[28:])
|
||||
if self.username.decode('utf-8') not in self.authdata:
|
||||
@@ -152,7 +152,7 @@ class ServerSession(ipmisession.Session):
|
||||
).digest()
|
||||
authcode = struct.pack("%dB" % len(data[8:]), *data[8:])
|
||||
if expectedauthcode != authcode:
|
||||
#TODO(jjohnson2): RMCP error back at invalid rakp3
|
||||
# TODO(jjohnson2): RMCP error back at invalid rakp3
|
||||
return
|
||||
clienttag = data[0]
|
||||
if data[1] != 0:
|
||||
@@ -207,7 +207,7 @@ class ServerSession(ipmisession.Session):
|
||||
After the session inactivity timeout, this invalidate the client
|
||||
session.
|
||||
"""
|
||||
#for now, we will have a non-configurable 60 second timeout
|
||||
# for now, we will have a non-configurable 60 second timeout
|
||||
pass
|
||||
|
||||
def _handle_channel_auth_cap(self, request):
|
||||
@@ -226,9 +226,9 @@ class ServerSession(ipmisession.Session):
|
||||
|
||||
|
||||
class IpmiServer(object):
|
||||
#auth capabilities for now is a static payload
|
||||
#for now always completion code 0, otherwise ignore
|
||||
#authentication type fixed to ipmi2, ipmi1 forbidden
|
||||
# auth capabilities for now is a static payload
|
||||
# for now always completion code 0, otherwise ignore
|
||||
# authentication type fixed to ipmi2, ipmi1 forbidden
|
||||
# 0b10000000
|
||||
|
||||
def __init__(self, authdata, port=623, bmcuuid=None, address='::'):
|
||||
|
||||
@@ -42,31 +42,37 @@ except AttributeError:
|
||||
def dictitems(d):
|
||||
return d.items()
|
||||
|
||||
|
||||
initialtimeout = 0.5 # minimum timeout for first packet to retry in any given
|
||||
# session. This will be randomized to stagger out retries
|
||||
# in case of congestion
|
||||
iothread = None # the thread in which all IO will be performed
|
||||
# While the model as-is works fine for it's own coroutine
|
||||
# structure, when combined with threading or something like
|
||||
# eventlet, it becomes difficult for the calling code to cope
|
||||
# This thread will tuck away the threading situation such that
|
||||
# calling code doesn't have to do any gymnastics to cope with
|
||||
# the nature of things.
|
||||
iothreadready = False # whether io thread is yet ready to work
|
||||
iothreadwaiters = [] # threads waiting for iothreadready
|
||||
# minimum timeout for first packet to retry in any given
|
||||
# session. This will be randomized to stagger out retries
|
||||
# in case of congestion
|
||||
initialtimeout = 0.5
|
||||
# the thread in which all IO will be performed
|
||||
# While the model as-is works fine for it's own coroutine
|
||||
# structure, when combined with threading or something like
|
||||
# eventlet, it becomes difficult for the calling code to cope
|
||||
# This thread will tuck away the threading situation such that
|
||||
# calling code doesn't have to do any gymnastics to cope with
|
||||
# the nature of things.
|
||||
iothread = None
|
||||
# whether io thread is yet ready to work
|
||||
iothreadready = False
|
||||
# threads waiting for iothreadready
|
||||
iothreadwaiters = []
|
||||
ioqueue = collections.deque([])
|
||||
myself = None
|
||||
ipv6support = None
|
||||
selectdeadline = 0
|
||||
running = True
|
||||
iosockets = [] # set of iosockets that will be shared amongst Session objects
|
||||
MAX_BMCS_PER_SOCKET = 64 # no more than this many BMCs will share a socket
|
||||
# this could be adjusted based on rmem_max
|
||||
# value, leading to fewer filehandles
|
||||
# set of iosockets that will be shared amongst Session objects
|
||||
iosockets = []
|
||||
# no more than this many BMCs will share a socket
|
||||
# this could be adjusted based on rmem_max
|
||||
# value, leading to fewer filehandles
|
||||
MAX_BMCS_PER_SOCKET = 64
|
||||
|
||||
MAX_IDLE = 29 # maximum time to allow idle, more than this and BMC may assume
|
||||
# incorrect idle
|
||||
# maximum time to allow idle, more than this and BMC may assume
|
||||
MAX_IDLE = 29
|
||||
# incorrect idle
|
||||
|
||||
|
||||
def define_worker():
|
||||
@@ -134,6 +140,7 @@ def define_worker():
|
||||
directediowaiters[workitem[2]].append(workitem)
|
||||
else:
|
||||
directediowaiters[workitem[2]] = [workitem]
|
||||
|
||||
return _IOWorker
|
||||
|
||||
|
||||
@@ -160,7 +167,7 @@ def _io_wait(timeout, myaddr=None, evq=None):
|
||||
|
||||
|
||||
def _io_sendto(mysocket, packet, sockaddr):
|
||||
#Want sendto to act reasonably sane..
|
||||
# Want sendto to act reasonably sane..
|
||||
mysocket.setblocking(1)
|
||||
if hasattr(mysocket, 'fd'):
|
||||
mysocket = mysocket.fd
|
||||
@@ -235,7 +242,7 @@ def _aespad(data):
|
||||
currlen = len(data) + 1 # need to count the pad length field as well
|
||||
neededpad = currlen % 16
|
||||
if neededpad: # if it happens to be zero, hurray, but otherwise invert the
|
||||
# sense of the padding
|
||||
# sense of the padding
|
||||
neededpad = 16 - neededpad
|
||||
padval = 1
|
||||
while padval <= neededpad:
|
||||
@@ -279,11 +286,11 @@ class Session(object):
|
||||
keepalive_sessions = {}
|
||||
peeraddr_to_nodes = {}
|
||||
iterwaiters = []
|
||||
#NOTE(jbjohnso):
|
||||
#socketpool is a mapping of sockets to usage count
|
||||
# NOTE(jbjohnso):
|
||||
# socketpool is a mapping of sockets to usage count
|
||||
socketpool = {}
|
||||
#this will be a lock. Delay the assignment so that a calling framework
|
||||
#can do something like reassign our threading and select modules
|
||||
# this will be a lock. Delay the assignment so that a calling framework
|
||||
# can do something like reassign our threading and select modules
|
||||
socketchecking = None
|
||||
|
||||
@classmethod
|
||||
@@ -436,8 +443,8 @@ class Session(object):
|
||||
self.cleaningup = False
|
||||
self.lastpayload = None
|
||||
self._customkeepalives = None
|
||||
self.evq = collections.deque([]) # queue of events denoting line to
|
||||
# run a cmd
|
||||
# queue of events denoting line to run a cmd
|
||||
self.evq = collections.deque([])
|
||||
self.bmc = bmc
|
||||
self.broken = False
|
||||
# a private queue for packets for which this session handler
|
||||
@@ -492,11 +499,11 @@ class Session(object):
|
||||
self.logging = False
|
||||
if self._customkeepalives:
|
||||
for ka in list(self._customkeepalives):
|
||||
# Be thorough and notify parties through their custom
|
||||
# keepalives. In practice, this *should* be the same, but
|
||||
# if a code somehow makes duplicate SOL handlers,
|
||||
# this would notify all the handlers rather than just the
|
||||
# last one to take ownership
|
||||
# Be thorough and notify parties through their custom
|
||||
# keepalives. In practice, this *should* be the same, but
|
||||
# if a code somehow makes duplicate SOL handlers,
|
||||
# this would notify all the handlers rather than just the
|
||||
# last one to take ownership
|
||||
self._customkeepalives[ka][1](
|
||||
{'error': 'Session Disconnected'})
|
||||
self._customkeepalives = None
|
||||
@@ -568,10 +575,10 @@ class Session(object):
|
||||
head = [constants.IPMI_BMC_ADDRESS,
|
||||
constants.netfn_codes['application'] << 2]
|
||||
check_sum = _checksum(*head)
|
||||
#NOTE(fengqian): according IPMI Figure 14-11, rqSWID is set to 81h
|
||||
# NOTE(fengqian): according IPMI Figure 14-11, rqSWID is set to 81h
|
||||
boday = [0x81, self.seqlun, constants.IPMI_SEND_MESSAGE_CMD,
|
||||
0x40 | channel]
|
||||
#NOTE(fengqian): Track request
|
||||
# NOTE(fengqian): Track request
|
||||
self._add_request_entry((constants.netfn_codes['application'] + 1,
|
||||
self.seqlun, constants.IPMI_SEND_MESSAGE_CMD))
|
||||
return head + [check_sum] + boday
|
||||
@@ -597,17 +604,17 @@ class Session(object):
|
||||
"""
|
||||
bridge_msg = []
|
||||
self.expectedcmd = command
|
||||
self.expectednetfn = netfn + \
|
||||
1 # in ipmi, the response netfn is always one
|
||||
# higher than the request payload, we assume
|
||||
# we are always the requestor for now
|
||||
# in ipmi, the response netfn is always one
|
||||
self.expectednetfn = netfn + 1
|
||||
# higher than the request payload, we assume
|
||||
# we are always the requestor for now
|
||||
seqincrement = 7 # IPMI spec forbids gaps bigger then 7 in seq number.
|
||||
# Risk the taboo rather than violate the rules
|
||||
# Risk the taboo rather than violate the rules
|
||||
while (not self.servermode and
|
||||
(netfn, command, self.seqlun) in self.tabooseq and
|
||||
self.tabooseq[(netfn, command, self.seqlun)] and seqincrement):
|
||||
self.tabooseq[(self.expectednetfn, command, self.seqlun)] -= 1
|
||||
# Allow taboo to eventually expire after a few rounds
|
||||
# Allow taboo to eventually expire after a few rounds
|
||||
self.seqlun += 4 # the last two bits are lun, so add 4 to add 1
|
||||
self.seqlun &= 0xff # we only have one byte, wrap when exceeded
|
||||
seqincrement -= 1
|
||||
@@ -616,7 +623,7 @@ class Session(object):
|
||||
addr = bridge_request.get('addr', 0x0)
|
||||
channel = bridge_request.get('channel', 0x0)
|
||||
bridge_msg = self._make_bridge_request_msg(channel, netfn, command)
|
||||
#NOTE(fengqian): For bridge request, rsaddr is specified and
|
||||
# NOTE(fengqian): For bridge request, rsaddr is specified and
|
||||
# rqaddr is BMC address.
|
||||
rqaddr = constants.IPMI_BMC_ADDRESS
|
||||
rsaddr = addr
|
||||
@@ -625,7 +632,7 @@ class Session(object):
|
||||
rsaddr = constants.IPMI_BMC_ADDRESS
|
||||
if self.servermode:
|
||||
rsaddr = self.clientaddr
|
||||
#figure 13-4, first two bytes are rsaddr and
|
||||
# figure 13-4, first two bytes are rsaddr and
|
||||
# netfn, for non-bridge request, rsaddr is always 0x20 since we are
|
||||
# addressing BMC while rsaddr is specified forbridge request
|
||||
header = [rsaddr, netfn << 2]
|
||||
@@ -636,7 +643,7 @@ class Session(object):
|
||||
payload = header + [headsum] + reqbody + [bodysum]
|
||||
if bridge_request:
|
||||
payload = bridge_msg + payload
|
||||
#NOTE(fengqian): For bridge request, another check sum is needed.
|
||||
# NOTE(fengqian): For bridge request, another check sum is needed.
|
||||
tail_csum = _checksum(*payload[3:])
|
||||
payload.append(tail_csum)
|
||||
|
||||
@@ -714,12 +721,12 @@ class Session(object):
|
||||
timeout = None
|
||||
else: # if not retry, give it a second before surrending
|
||||
timeout = 1
|
||||
#The event loop is shared amongst pyghmi session instances
|
||||
#within a process. In this way, synchronous usage of the interface
|
||||
#plays well with asynchronous use. In fact, this produces the behavior
|
||||
#of only the constructor needing a callback. From then on,
|
||||
#synchronous usage of the class acts in a greenthread style governed by
|
||||
#order of data on the network
|
||||
# The event loop is shared amongst pyghmi session instances
|
||||
# within a process. In this way, synchronous usage of the interface
|
||||
# plays well with asynchronous use. In fact, this produces the
|
||||
# behavior of only the constructor needing a callback. From then on,
|
||||
# synchronous usage of the class acts in a greenthread style governed
|
||||
# by order of data on the network
|
||||
self.awaitresponse(retry, waitall)
|
||||
lastresponse = self.lastresponse
|
||||
self.incommand = False
|
||||
@@ -759,12 +766,12 @@ class Session(object):
|
||||
:param timeout: Specify a custom timeout for long-running request
|
||||
"""
|
||||
if payload and self.lastpayload:
|
||||
# we already have a packet outgoing, make this
|
||||
# a pending payload
|
||||
# this way a simplistic BMC won't get confused
|
||||
# and we also avoid having to do more complicated
|
||||
# retry mechanism where each payload is
|
||||
# retried separately
|
||||
# we already have a packet outgoing, make this
|
||||
# a pending payload
|
||||
# this way a simplistic BMC won't get confused
|
||||
# and we also avoid having to do more complicated
|
||||
# retry mechanism where each payload is
|
||||
# retried separately
|
||||
self.pendingpayloads.append((payload, payload_type, retry))
|
||||
return
|
||||
if payload_type is None:
|
||||
@@ -784,7 +791,7 @@ class Session(object):
|
||||
if self.ipmiversion == 2.0:
|
||||
message.append(payload_type)
|
||||
if baretype == 2:
|
||||
#TODO(jbjohnso): OEM payload types
|
||||
# TODO(jbjohnso): OEM payload types
|
||||
raise NotImplementedError("OEM Payloads")
|
||||
elif baretype not in constants.payload_types.values():
|
||||
raise NotImplementedError(
|
||||
@@ -797,23 +804,22 @@ class Session(object):
|
||||
message += self._ipmi15authcode(payload)
|
||||
message.append(len(payload))
|
||||
message += payload
|
||||
totlen = 34 + \
|
||||
len(message) # Guessing the ipmi spec means the whole
|
||||
# packet and assume no tag in old 1.5 world
|
||||
# Guessing the ipmi spec means the whole
|
||||
totlen = 34 + len(message)
|
||||
# packet and assume no tag in old 1.5 world
|
||||
if totlen in (56, 84, 112, 128, 156):
|
||||
message.append(0) # Legacy pad as mandated by ipmi spec
|
||||
elif self.ipmiversion == 2.0:
|
||||
psize = len(payload)
|
||||
if self.confalgo:
|
||||
pad = (
|
||||
psize + 1) % 16 # pad has to cope with one byte field like
|
||||
# the _aespad function
|
||||
pad = (psize + 1) % 16 # pad has to cope with one byte
|
||||
# field like the _aespad function
|
||||
if pad: # if no pad needed, then we take no more action
|
||||
pad = 16 - pad
|
||||
newpsize = psize + pad + \
|
||||
17 # new payload size grew according to pad
|
||||
# size, plus pad length, plus 16 byte IV
|
||||
#(Table 13-20)
|
||||
# new payload size grew according to pad
|
||||
newpsize = psize + pad + 17
|
||||
# size, plus pad length, plus 16 byte IV
|
||||
# (Table 13-20)
|
||||
message.append(newpsize & 0xff)
|
||||
message.append(newpsize >> 8)
|
||||
iv = os.urandom(16)
|
||||
@@ -830,26 +836,26 @@ class Session(object):
|
||||
message.append(psize >> 8)
|
||||
message += list(payload)
|
||||
if self.integrityalgo: # see table 13-8,
|
||||
# RMCP+ packet format
|
||||
# TODO(jbjohnso): SHA256 which is now
|
||||
# allowed
|
||||
# RMCP+ packet format
|
||||
# TODO(jbjohnso): SHA256 which is now
|
||||
# allowed
|
||||
neededpad = (len(message) - 2) % 4
|
||||
if neededpad:
|
||||
neededpad = 4 - neededpad
|
||||
message += [0xff] * neededpad
|
||||
message.append(neededpad)
|
||||
message.append(7) # reserved, 7 is the required value for the
|
||||
# specification followed
|
||||
# specification followed
|
||||
integdata = message[4:]
|
||||
authcode = hmac.new(self.k1,
|
||||
struct.pack("%dB" % len(integdata),
|
||||
*integdata),
|
||||
hashlib.sha1).digest()[:12] # SHA1-96
|
||||
# per RFC2404 truncates to 96 bits
|
||||
# per RFC2404 truncates to 96 bits
|
||||
message += struct.unpack("12B", authcode)
|
||||
self.netpacket = struct.pack("!%dB" % len(message), *message)
|
||||
#advance idle timer since we don't need keepalive while sending packets
|
||||
#out naturally
|
||||
# advance idle timer since we don't need keepalive while sending
|
||||
# packets out naturally
|
||||
if (self in Session.keepalive_sessions and not needskeepalive and
|
||||
not self._customkeepalives):
|
||||
Session.keepalive_sessions[self]['timeout'] = _monotonic_time() + \
|
||||
@@ -857,10 +863,11 @@ class Session(object):
|
||||
self._xmit_packet(retry, delay_xmit=delay_xmit, timeout=timeout)
|
||||
|
||||
def _ipmi15authcode(self, payload, checkremotecode=False):
|
||||
#checkremotecode is used to verify remote code,
|
||||
#otherwise this function is used to general authcode for local
|
||||
if self.authtype == 0: # Only for things before auth in ipmi 1.5, not
|
||||
# like 2.0 cipher suite 0
|
||||
# checkremotecode is used to verify remote code,
|
||||
# otherwise this function is used to general authcode for local
|
||||
if self.authtype == 0:
|
||||
# Only for things before auth in ipmi 1.5, not
|
||||
# like 2.0 cipher suite 0
|
||||
return ()
|
||||
password = self.password
|
||||
padneeded = 16 - len(password)
|
||||
@@ -920,13 +927,14 @@ class Session(object):
|
||||
self.sessionid = struct.unpack("<I", struct.pack("4B", *data[0:4]))[0]
|
||||
self.authtype = 2
|
||||
self._activate_session(data[4:])
|
||||
|
||||
# NOTE(jbjohnso):
|
||||
# This sends the activate session payload. We pick '1' as the requested
|
||||
# sequence number without perturbing our real sequence number
|
||||
|
||||
def _activate_session(self, data):
|
||||
rqdata = [2, 4] + list(data) + [1, 0, 0, 0]
|
||||
# TODO(jbjohnso): this always requests admin level (1.5)
|
||||
# TODO(jbjohnso): this always requests admin level (1.5)
|
||||
self.ipmicallback = self._activated_session
|
||||
self._send_ipmi_net_payload(netfn=0x6, command=0x3a, data=rqdata)
|
||||
|
||||
@@ -981,8 +989,9 @@ class Session(object):
|
||||
|
||||
def _open_rmcpplus_request(self):
|
||||
self.authtype = 6
|
||||
self.localsid += 1 # have unique local session ids to ignore aborted
|
||||
# login attempts from the past
|
||||
# have unique local session ids to ignore aborted
|
||||
# login attempts from the past
|
||||
self.localsid += 1
|
||||
self.rmcptag += 1
|
||||
data = [
|
||||
self.rmcptag,
|
||||
@@ -994,7 +1003,7 @@ class Session(object):
|
||||
0, 0, 0, 8, 1, 0, 0, 0, # table 13-17, SHA-1
|
||||
1, 0, 0, 8, 1, 0, 0, 0, # SHA-1 integrity
|
||||
2, 0, 0, 8, 1, 0, 0, 0, # AES privacy
|
||||
#2,0,0,8,0,0,0,0, #no privacy confalgo
|
||||
# 2,0,0,8,0,0,0,0, #no privacy confalgo
|
||||
]
|
||||
self.sessioncontext = 'OPENSESSION'
|
||||
self.send_payload(
|
||||
@@ -1035,12 +1044,12 @@ class Session(object):
|
||||
unspecified, will autodetect based on earliest timeout
|
||||
"""
|
||||
global iosockets
|
||||
#Assume:
|
||||
#Instance A sends request to packet B
|
||||
#Then Instance C sends request to BMC D
|
||||
#BMC D was faster, so data comes back before BMC B
|
||||
#Instance C gets to go ahead of Instance A, because
|
||||
#Instance C can get work done, but instance A cannot
|
||||
# Assume:
|
||||
# Instance A sends request to packet B
|
||||
# Then Instance C sends request to BMC D
|
||||
# BMC D was faster, so data comes back before BMC B
|
||||
# Instance C gets to go ahead of Instance A, because
|
||||
# Instance C can get work done, but instance A cannot
|
||||
|
||||
curtime = _monotonic_time()
|
||||
# There ar a number of parties that each has their own timeout
|
||||
@@ -1098,12 +1107,11 @@ class Session(object):
|
||||
session._keepalive()
|
||||
for session, parms in dictitems(cls.waiting_sessions):
|
||||
if parms['timeout'] < curtime: # timeout has expired, time to
|
||||
# give up on it and trigger timeout
|
||||
# response in the respective
|
||||
# session
|
||||
sessionstodel.append(
|
||||
session) # defer deletion until after loop
|
||||
# to avoid confusing the for loop
|
||||
# give up on it and trigger timeout
|
||||
# response in the respective session
|
||||
# defer deletion until after loop
|
||||
sessionstodel.append(session)
|
||||
# to avoid confusing the for loop
|
||||
for session in sessionstodel:
|
||||
cls.waiting_sessions.pop(session, None)
|
||||
# one loop iteration to make sure recursion doesn't induce redundant
|
||||
@@ -1113,7 +1121,7 @@ class Session(object):
|
||||
return len(cls.waiting_sessions)
|
||||
|
||||
def register_keepalive(self, cmd, callback):
|
||||
'''Register custom keepalive IPMI command
|
||||
"""Register custom keepalive IPMI command
|
||||
|
||||
This is mostly intended for use by the console code.
|
||||
calling code would have an easier time just scheduling in their
|
||||
@@ -1124,7 +1132,7 @@ class Session(object):
|
||||
:param callback: A function to be called with results of the keepalive
|
||||
|
||||
:returns: value to identify registration for unregister_keepalive
|
||||
'''
|
||||
"""
|
||||
regid = random.random()
|
||||
if self._customkeepalives is None:
|
||||
self._customkeepalives = {regid: (cmd, callback)}
|
||||
@@ -1198,8 +1206,8 @@ class Session(object):
|
||||
sockaddr is not None and
|
||||
self.sockaddr != sockaddr):
|
||||
return # here, we might have sent an ipv4 and ipv6 packet to kick
|
||||
# things off ignore the second reply since we have one
|
||||
# satisfactory answer
|
||||
# things off ignore the second reply since we have one
|
||||
# satisfactory answer
|
||||
if data[4] in (0, 2): # This is an ipmi 1.5 paylod
|
||||
remsequencenumber = struct.unpack('<I', bytes(data[5:9]))[0]
|
||||
remsessid = struct.unpack("<I", bytes(data[9:13]))[0]
|
||||
@@ -1218,9 +1226,10 @@ class Session(object):
|
||||
return -5 # remote sequence number is too low, reject it
|
||||
self.remsequencenumber = remsequencenumber
|
||||
if data[4] != self.authtype:
|
||||
return -2 # BMC responded with mismatch authtype, for
|
||||
# mutual authentication reject it. If this causes
|
||||
# legitimate issues, it's the vendor's fault
|
||||
# BMC responded with mismatch authtype, for
|
||||
# mutual authentication reject it. If this causes
|
||||
# legitimate issues, it's the vendor's fault
|
||||
return -2
|
||||
|
||||
if remsessid != self.sessionid:
|
||||
return -1 # does not match our session id, drop it
|
||||
@@ -1231,7 +1240,7 @@ class Session(object):
|
||||
if data[4] == 2: # we have authcode in this ipmi 1.5 packet
|
||||
authcode = data[13:29]
|
||||
del rsp[13:29]
|
||||
# this is why we needed a mutable representation
|
||||
# this is why we needed a mutable representation
|
||||
payload = list(rsp[14:14 + rsp[13]])
|
||||
if authcode:
|
||||
expectedauthcode = self._ipmi15authcode(payload,
|
||||
@@ -1251,7 +1260,7 @@ class Session(object):
|
||||
pass
|
||||
|
||||
def _got_rakp3(self, data):
|
||||
#stub, client sessions ignore rakp3
|
||||
# stub, client sessions ignore rakp3
|
||||
pass
|
||||
|
||||
def _got_rmcp_openrequest(self, data):
|
||||
@@ -1277,8 +1286,8 @@ class Session(object):
|
||||
# If endorsing a shared secret scheme, then at the very least it
|
||||
# needs to do mutual assurance
|
||||
if not (data[5] & 0b01000000): # This would be the line that might
|
||||
# trip up some insecure BMC
|
||||
# implementation
|
||||
# trip up some insecure BMC
|
||||
# implementation
|
||||
return
|
||||
encrypted = 0
|
||||
if data[5] & 0b10000000:
|
||||
@@ -1378,9 +1387,10 @@ class Session(object):
|
||||
|
||||
def _got_rakp2(self, data):
|
||||
if not (self.sessioncontext in ('EXPECTINGRAKP2', 'EXPECTINGRAKP4')):
|
||||
return -9 # if we are not expecting rakp2, ignore. In a retry
|
||||
# scenario, replying from stale RAKP2 after sending
|
||||
# RAKP3 seems to be best
|
||||
# if we are not expecting rakp2, ignore. In a retry
|
||||
# scenario, replying from stale RAKP2 after sending
|
||||
# RAKP3 seems to be best
|
||||
return -9
|
||||
if data[0] != self.rmcptag: # ignore mismatched tags for retry logic
|
||||
return -9
|
||||
if data[1] != 0: # if not successful, consider next move
|
||||
@@ -1390,8 +1400,9 @@ class Session(object):
|
||||
self.privlevel = 3
|
||||
self.login()
|
||||
return
|
||||
if data[1] == 2: # invalid sessionid 99% of the time means a retry
|
||||
# scenario invalidated an in-flight transaction
|
||||
# invalid sessionid 99% of the time means a retry
|
||||
# scenario invalidated an in-flight transaction
|
||||
if data[1] == 2:
|
||||
return
|
||||
if data[1] in constants.rmcp_codes:
|
||||
errstr = constants.rmcp_codes[data[1]]
|
||||
@@ -1456,12 +1467,13 @@ class Session(object):
|
||||
return -9
|
||||
if data[1] != 0:
|
||||
if data[1] == 2 and self.logontries: # if we retried RAKP3 because
|
||||
# RAKP4 got dropped, BMC can consider it done and we must
|
||||
# restart
|
||||
# RAKP4 got dropped, BMC can consider it done and we must
|
||||
# restart
|
||||
self._relog()
|
||||
if data[1] == 15 and self.logontries: # ignore 15 value if we are
|
||||
# retrying. xCAT did but I can't recall why exactly
|
||||
# TODO(jbjohnso) jog my memory to update the comment
|
||||
# ignore 15 value if we are retrying.
|
||||
# xCAT did but I can't recall why exactly
|
||||
if data[1] == 15 and self.logontries:
|
||||
# TODO(jbjohnso) jog my memory to update the comment
|
||||
return
|
||||
if data[1] in constants.rmcp_codes:
|
||||
errstr = constants.rmcp_codes[data[1]]
|
||||
@@ -1506,18 +1518,18 @@ class Session(object):
|
||||
if self._lookup_request_entry(entry):
|
||||
self._remove_request_entry(entry)
|
||||
|
||||
#NOTE(fengqian): for bridge request, we need to handle the response
|
||||
#twice. First response shows if message send correctly, second
|
||||
#response is the real response.
|
||||
#If the message is send crrectly, we will discard the first
|
||||
#response or else error message will be parsed and return.
|
||||
if ((entry[0] in [0x06, 0x07]) and (entry[2] == 0x34)
|
||||
and (payload[-2] == 0x0)):
|
||||
# NOTE(fengqian): for bridge request, we need to handle the
|
||||
# response twice. First response shows if message send correctly,
|
||||
# second response is the real response.
|
||||
# If the message is send crrectly, we will discard the first
|
||||
# response or else error message will be parsed and return.
|
||||
if ((entry[0] in [0x06, 0x07]) and (entry[2] == 0x34) and
|
||||
(payload[-2] == 0x0)):
|
||||
return -1
|
||||
else:
|
||||
self._parse_payload(payload)
|
||||
#NOTE(fengqian): recheck if the certain entry is removed in
|
||||
#case that bridge request failed.
|
||||
# NOTE(fengqian): recheck if the certain entry is removed in
|
||||
# case that bridge request failed.
|
||||
if self.request_entry:
|
||||
self._remove_request_entry((self.expectednetfn,
|
||||
self.seqlun, self.expectedcmd))
|
||||
@@ -1529,25 +1541,28 @@ class Session(object):
|
||||
def _parse_payload(self, payload):
|
||||
if hasattr(self, 'hasretried') and self.hasretried:
|
||||
self.hasretried = 0
|
||||
# try to skip it for at most 16 cycles of overflow
|
||||
self.tabooseq[
|
||||
(self.expectednetfn, self.expectedcmd, self.seqlun)] = 16
|
||||
# try to skip it for at most 16 cycles of overflow
|
||||
# We want to now remember that we do not have an expected packet
|
||||
self.expectednetfn = 0x1ff # bigger than one byte means it can never
|
||||
# match the one byte value by mistake
|
||||
# bigger than one byte means it can never match the one byte value
|
||||
# by mistake
|
||||
self.expectednetfn = 0x1ff
|
||||
self.expectedcmd = 0x1ff
|
||||
if not self.servermode:
|
||||
self.seqlun += 4 # prepare seqlun for next transmit
|
||||
self.seqlun &= 0xff # when overflowing, wrap around
|
||||
Session.waiting_sessions.pop(self, None)
|
||||
self.lastpayload = None # render retry mechanism utterly incapable of
|
||||
# doing anything, though it shouldn't matter
|
||||
# render retry mechanism utterly incapable of
|
||||
# doing anything, though it shouldn't matter
|
||||
self.lastpayload = None
|
||||
self.last_payload_type = None
|
||||
response = {}
|
||||
response['netfn'] = payload[1] >> 2
|
||||
del payload[0:5]
|
||||
# ^^ remove header of rsaddr/netfn/lun/checksum/rq/seq/lun
|
||||
del payload[-1] # remove the trailing checksum
|
||||
del payload[0:5]
|
||||
# remove the trailing checksum
|
||||
del payload[-1]
|
||||
response['command'] = payload[0]
|
||||
if self.servermode:
|
||||
del payload[0:1]
|
||||
@@ -1593,21 +1608,21 @@ class Session(object):
|
||||
self.lastpayload = None
|
||||
self._relog()
|
||||
else: # in IPMI case, the only recourse is to act as if the packet is
|
||||
# idempotent. SOL has more sophisticated retry handling
|
||||
# the biggest risks are reset sp which is often fruitless to retry
|
||||
# and chassis reset, which sometimes will shoot itself
|
||||
# systematically in the head in a shared port case making replies
|
||||
# impossible
|
||||
# idempotent. SOL has more sophisticated retry handling
|
||||
# the biggest risks are reset sp which is often fruitless to retry
|
||||
# and chassis reset, which sometimes will shoot itself
|
||||
# systematically in the head in a shared port case making replies
|
||||
# impossible
|
||||
self.hasretried = 1 # remember so that we can track taboo
|
||||
# combinations
|
||||
# of sequence number, netfn, and lun due to
|
||||
# ambiguity on the wire
|
||||
# combinations
|
||||
# of sequence number, netfn, and lun due to
|
||||
# ambiguity on the wire
|
||||
self.send_payload()
|
||||
self.nowait = False
|
||||
|
||||
def _xmit_packet(self, retry=True, delay_xmit=None, timeout=None):
|
||||
if self.sequencenumber: # seq number of zero will be left alone, it is
|
||||
# special, otherwise increment
|
||||
# special, otherwise increment
|
||||
self.sequencenumber += 1
|
||||
if delay_xmit is not None:
|
||||
Session.waiting_sessions[self] = {}
|
||||
@@ -1617,8 +1632,9 @@ class Session(object):
|
||||
return # skip transmit, let retry timer do it's thing
|
||||
if self.sockaddr:
|
||||
_io_sendto(self.socket, self.netpacket, self.sockaddr)
|
||||
else: # he have not yet picked a working sockaddr for this connection,
|
||||
# try all the candidates that getaddrinfo provides
|
||||
else:
|
||||
# he have not yet picked a working sockaddr for this connection,
|
||||
# try all the candidates that getaddrinfo provides
|
||||
self.allsockaddrs = []
|
||||
try:
|
||||
for res in socket.getaddrinfo(self.bmc,
|
||||
@@ -1667,6 +1683,7 @@ class Session(object):
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
ipmis = Session(bmc=sys.argv[1],
|
||||
userid=sys.argv[2],
|
||||
password=os.environ['IPMIPASS'])
|
||||
|
||||
@@ -84,8 +84,8 @@ def get_ipmi_error(response, suffix=""):
|
||||
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)]):
|
||||
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:
|
||||
|
||||
@@ -46,8 +46,8 @@ def ones_complement(value, bits):
|
||||
# complement prevalent in ipmi spec
|
||||
signbit = 0b1 << (bits - 1)
|
||||
if value & signbit:
|
||||
#if negative, subtract 1, then take 1s
|
||||
#complement given bits width
|
||||
# if negative, subtract 1, then take 1s
|
||||
# complement given bits width
|
||||
return 0 - (value ^ ((0b1 << bits) - 1))
|
||||
else:
|
||||
return value
|
||||
@@ -58,8 +58,8 @@ def twos_complement(value, bits):
|
||||
# complement prevalent in ipmi spec
|
||||
signbit = 0b1 << (bits - 1)
|
||||
if value & signbit:
|
||||
#if negative, subtract 1, then take 1s
|
||||
#complement given bits width
|
||||
# if negative, subtract 1, then take 1s
|
||||
# complement given bits width
|
||||
return 0 - ((value - 1) ^ ((0b1 << bits) - 1))
|
||||
else:
|
||||
return value
|
||||
@@ -261,7 +261,7 @@ class SDREntry(object):
|
||||
raise NotImplementedError
|
||||
self.rectype = entrybytes[3]
|
||||
self.linearization = None
|
||||
#most important to get going are 1, 2, and 11
|
||||
# most important to get going are 1, 2, and 11
|
||||
self.sdrtype = TYPE_SENSOR # assume a sensor
|
||||
if self.rectype == 1: # full sdr
|
||||
self.full_decode(entrybytes[5:])
|
||||
@@ -312,7 +312,7 @@ class SDREntry(object):
|
||||
|
||||
def association_decode(self, entry):
|
||||
# table 43-4 Entity Associaition Record
|
||||
#TODO(jbjohnso): actually represent this data
|
||||
# TODO(jbjohnso): actually represent this data
|
||||
self.sdrtype = TYPE_UNKNOWN
|
||||
|
||||
def compact_decode(self, entry):
|
||||
@@ -337,10 +337,10 @@ class SDREntry(object):
|
||||
except KeyError:
|
||||
self.sensor_type = "UNKNOWN type " + str(entry[7])
|
||||
self.reading_type = entry[8] # table 42-1
|
||||
# 0: unspecified
|
||||
# 1: generic threshold based
|
||||
# 0x6f: discrete sensor-specific from table 42-3, sensor offsets
|
||||
# all others per table 42-2, generic discrete
|
||||
# 0: unspecified
|
||||
# 1: generic threshold based
|
||||
# 0x6f: discrete sensor-specific from table 42-3, sensor offsets
|
||||
# all others per table 42-2, generic discrete
|
||||
# numeric format is one of:
|
||||
# 0 - unsigned, 1 - 1s complement, 2 - 2s complement, 3 - ignore number
|
||||
# compact records are supposed to always write it as '3', presumably
|
||||
@@ -371,8 +371,8 @@ class SDREntry(object):
|
||||
self.modunit
|
||||
|
||||
def full_decode(self, entry):
|
||||
#offsets are table from spec, minus 6
|
||||
#TODO(jbjohnso): table 43-13, put in constants to interpret entry[3]
|
||||
# offsets are table from spec, minus 6
|
||||
# TODO(jbjohnso): table 43-13, put in constants to interpret entry[3]
|
||||
self._common_decode(entry)
|
||||
# now must extract the formula data to transform values
|
||||
# entry[18 to entry[24].
|
||||
@@ -543,7 +543,7 @@ class SDREntry(object):
|
||||
(entry[4] & 0b11110000) << 2
|
||||
self.accuracyexp = (entry[4] & 0b1100) >> 2
|
||||
self.direction = entry[4] & 0b11
|
||||
#0 = n/a, 1 = input, 2 = output
|
||||
# 0 = n/a, 1 = input, 2 = output
|
||||
self.resultexponent = twos_complement((entry[5] & 0b11110000) >> 4, 4)
|
||||
bexponent = twos_complement(entry[5] & 0b1111, 4)
|
||||
# might as well do the math to 'b' now rather than wait for later
|
||||
@@ -594,7 +594,7 @@ class SDR(object):
|
||||
self.read_info()
|
||||
|
||||
def read_info(self):
|
||||
#first, we want to know the device id
|
||||
# first, we want to know the device id
|
||||
rsp = self.ipmicmd.xraw_command(netfn=6, command=1)
|
||||
rsp['data'] = bytearray(rsp['data'])
|
||||
self.device_id = rsp['data'][0]
|
||||
@@ -618,9 +618,10 @@ class SDR(object):
|
||||
# be able to proceed
|
||||
# However at the moment, we haven't done so
|
||||
raise NotImplementedError
|
||||
return # We have Device SDR, without SDR Repository device, but
|
||||
# also without sensor device support, no idea how to
|
||||
# continue
|
||||
return
|
||||
# We have Device SDR, without SDR Repository device, but
|
||||
# also without sensor device support, no idea how to
|
||||
# continue
|
||||
self.get_sdr()
|
||||
|
||||
def get_sdr_reservation(self):
|
||||
@@ -636,14 +637,14 @@ class SDR(object):
|
||||
# we only understand SDR version 51h, the only version defined
|
||||
# at time of this writing
|
||||
raise NotImplementedError
|
||||
#NOTE(jbjohnso): we actually don't need to care about 'numrecords'
|
||||
# NOTE(jbjohnso): we actually don't need to care about 'numrecords'
|
||||
# since FFFF marks the end explicitly
|
||||
#numrecords = (rsp['data'][2] << 8) + rsp['data'][1]
|
||||
#NOTE(jbjohnso): don't care about 'free space' at the moment
|
||||
#NOTE(jbjohnso): most recent timstamp data for add and erase could be
|
||||
# numrecords = (rsp['data'][2] << 8) + rsp['data'][1]
|
||||
# NOTE(jbjohnso): don't care about 'free space' at the moment
|
||||
# NOTE(jbjohnso): most recent timstamp data for add and erase could be
|
||||
# handy to detect cache staleness, but for now will assume invariant
|
||||
# over life of session
|
||||
#NOTE(jbjohnso): not looking to support the various options in op
|
||||
# NOTE(jbjohnso): not looking to support the various options in op
|
||||
# support, ignore those for now, reservation if some BMCs can't read
|
||||
# full SDR in one slurp
|
||||
recid = 0
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
# sake of typical internal management devices. Compatibility back to python
|
||||
# 2.6 as is found in commonly used enterprise linux distributions.
|
||||
|
||||
__author__ = 'jjohnson2'
|
||||
import json
|
||||
import pyghmi.exceptions as pygexc
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
try:
|
||||
import Cookie
|
||||
@@ -24,10 +27,8 @@ try:
|
||||
except ImportError:
|
||||
import http.client as httplib
|
||||
import http.cookies as Cookie
|
||||
import json
|
||||
import pyghmi.exceptions as pygexc
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
__author__ = 'jjohnson2'
|
||||
|
||||
|
||||
class SecureHTTPConnection(httplib.HTTPConnection, object):
|
||||
|
||||
Reference in New Issue
Block a user