python-ipmi 0.5.4__tar.gz → 0.5.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {python-ipmi-0.5.4/python_ipmi.egg-info → python_ipmi-0.5.7}/PKG-INFO +6 -3
  2. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/README.rst +2 -1
  3. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/__init__.py +2 -2
  4. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/bmc.py +15 -0
  5. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/chassis.py +3 -3
  6. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/constants.py +3 -0
  7. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/emulation.py +1 -1
  8. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/errors.py +11 -4
  9. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/fields.py +15 -3
  10. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/helper.py +1 -1
  11. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/hpm.py +3 -1
  12. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/interfaces/aardvark.py +13 -1
  13. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/interfaces/ipmb.py +29 -10
  14. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/interfaces/ipmitool.py +19 -1
  15. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/interfaces/rmcp.py +108 -42
  16. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/ipmitool.py +41 -6
  17. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/lan.py +10 -3
  18. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/messaging.py +4 -3
  19. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/bmc.py +6 -7
  20. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/chassis.py +0 -1
  21. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/constants.py +51 -4
  22. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/dcmi.py +0 -2
  23. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/device_messaging.py +3 -6
  24. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/event.py +0 -1
  25. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/fru.py +0 -1
  26. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/hpm.py +0 -1
  27. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/lan.py +0 -1
  28. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/message.py +1 -4
  29. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/picmg.py +0 -2
  30. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/registry.py +0 -1
  31. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/sdr.py +0 -1
  32. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/sel.py +0 -1
  33. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/sensor.py +0 -1
  34. python_ipmi-0.5.7/pyipmi/msgs/vita.py +584 -0
  35. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/sdr.py +4 -3
  36. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/sensor.py +10 -0
  37. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/session.py +13 -0
  38. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/utils.py +19 -0
  39. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/version.py +1 -1
  40. {python-ipmi-0.5.4 → python_ipmi-0.5.7/python_ipmi.egg-info}/PKG-INFO +6 -3
  41. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/python_ipmi.egg-info/SOURCES.txt +17 -2
  42. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/setup.py +2 -2
  43. python_ipmi-0.5.7/tests/test_bmc.py +57 -0
  44. python_ipmi-0.5.7/tests/test_chassis.py +71 -0
  45. python_ipmi-0.5.7/tests/test_device_messaging.py +22 -0
  46. python_ipmi-0.5.7/tests/test_errors.py +48 -0
  47. python_ipmi-0.5.7/tests/test_fields.py +33 -0
  48. python_ipmi-0.5.7/tests/test_fru.py +74 -0
  49. python_ipmi-0.5.7/tests/test_helper.py +32 -0
  50. python_ipmi-0.5.7/tests/test_hpm.py +94 -0
  51. python_ipmi-0.5.7/tests/test_ipmi.py +156 -0
  52. python_ipmi-0.5.7/tests/test_ipmitool.py +36 -0
  53. python_ipmi-0.5.7/tests/test_lan.py +67 -0
  54. python_ipmi-0.5.7/tests/test_picmg.py +64 -0
  55. python_ipmi-0.5.7/tests/test_sdr.py +322 -0
  56. python_ipmi-0.5.7/tests/test_sel.py +154 -0
  57. python_ipmi-0.5.7/tests/test_sensor.py +145 -0
  58. python_ipmi-0.5.7/tests/test_utils.py +168 -0
  59. python-ipmi-0.5.4/pyipmi/msgs/vita.py +0 -49
  60. python-ipmi-0.5.4/python_ipmi.egg-info/requires.txt +0 -1
  61. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/AUTHORS +0 -0
  62. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/COPYING +0 -0
  63. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/dcmi.py +0 -0
  64. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/event.py +0 -0
  65. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/fru.py +0 -0
  66. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/interfaces/__init__.py +0 -0
  67. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/interfaces/ipmbdev.py +0 -0
  68. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/interfaces/mock.py +0 -0
  69. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/logger.py +0 -0
  70. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/__init__.py +0 -0
  71. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/msgs/session.py +0 -0
  72. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/picmg.py +0 -0
  73. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/sel.py +0 -0
  74. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/pyipmi/state.py +0 -0
  75. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/python_ipmi.egg-info/dependency_links.txt +0 -0
  76. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/python_ipmi.egg-info/entry_points.txt +0 -0
  77. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/python_ipmi.egg-info/top_level.txt +0 -0
  78. {python-ipmi-0.5.4 → python_ipmi-0.5.7}/setup.cfg +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-ipmi
3
- Version: 0.5.4
3
+ Version: 0.5.7
4
4
  Summary: Pure python IPMI library
5
5
  Home-page: https://github.com/kontron/python-ipmi
6
- Download-URL: https://github.com/kontron/python-ipmi/tarball/0.5.4
6
+ Download-URL: https://github.com/kontron/python-ipmi/tarball/0.5.7
7
7
  Author: Michael Walle, Heiko Thiery
8
8
  Author-email: michael.walle@kontron.com, heiko.thiery@kontron.com
9
9
  License: LGPLv2+
@@ -19,6 +19,8 @@ Classifier: Programming Language :: Python :: 3.8
19
19
  Classifier: Programming Language :: Python :: 3.9
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
22
24
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
25
  License-File: COPYING
24
26
  License-File: AUTHORS
@@ -104,6 +106,7 @@ Example with lan interface:
104
106
 
105
107
  connection.session.set_session_type_rmcp('10.0.0.1', port=623)
106
108
  connection.session.set_auth_type_user('admin', 'admin')
109
+ connection.session.set_priv_level("ADMINISTRATOR")
107
110
  connection.session.establish()
108
111
 
109
112
  connection.get_device_id()
@@ -112,7 +115,7 @@ ipmitool command:
112
115
 
113
116
  .. code:: shell
114
117
 
115
- ipmitool -I lan -H 10.0.0.1 -p 623 -U "admin" -P "admin" -t 0x82 -b 0 -l 0 raw 0x06 0x01
118
+ ipmitool -I lan -H 10.0.0.1 -p 623 -L "ADMINISTRATOR" -U "admin" -P "admin" -t 0x82 -b 0 -l 0 raw 0x06 0x01
116
119
 
117
120
 
118
121
  Example with serial interface:
@@ -79,6 +79,7 @@ Example with lan interface:
79
79
 
80
80
  connection.session.set_session_type_rmcp('10.0.0.1', port=623)
81
81
  connection.session.set_auth_type_user('admin', 'admin')
82
+ connection.session.set_priv_level("ADMINISTRATOR")
82
83
  connection.session.establish()
83
84
 
84
85
  connection.get_device_id()
@@ -87,7 +88,7 @@ ipmitool command:
87
88
 
88
89
  .. code:: shell
89
90
 
90
- ipmitool -I lan -H 10.0.0.1 -p 623 -U "admin" -P "admin" -t 0x82 -b 0 -l 0 raw 0x06 0x01
91
+ ipmitool -I lan -H 10.0.0.1 -p 623 -L "ADMINISTRATOR" -U "admin" -P "admin" -t 0x82 -b 0 -l 0 raw 0x06 0x01
91
92
 
92
93
 
93
94
  Example with serial interface:
@@ -37,7 +37,7 @@ from . import msgs
37
37
  from .errors import IpmiTimeoutError, CompletionCodeError, RetryError
38
38
  from .msgs.registry import create_request_by_name
39
39
  from .session import Session
40
- from .utils import check_completion_code, is_string
40
+ from .utils import check_rsp_completion_code, is_string
41
41
 
42
42
  try:
43
43
  from version import __version__
@@ -210,7 +210,7 @@ class Ipmi(bmc.Bmc, chassis.Chassis, dcmi.Dcmi, fru.Fru, picmg.Picmg, hpm.Hpm,
210
210
  setattr(req, key, value)
211
211
 
212
212
  rsp = self.send_message(req)
213
- check_completion_code(rsp.completion_code)
213
+ check_rsp_completion_code(rsp)
214
214
  return rsp
215
215
 
216
216
  def raw_command(self, lun, netfn, raw_bytes):
@@ -24,6 +24,9 @@ class Bmc(object):
24
24
  def get_device_id(self):
25
25
  return DeviceId(self.send_message_with_name('GetDeviceId'))
26
26
 
27
+ def get_device_guid(self):
28
+ return DeviceGuid(self.send_message_with_name('GetDeviceGuid'))
29
+
27
30
  def cold_reset(self):
28
31
  self.send_message_with_name('ColdReset')
29
32
 
@@ -121,6 +124,7 @@ class DeviceId(State):
121
124
  string += ' ipmi: %s' % self.ipmi_version
122
125
  string += ' manufacturer: %d' % self.manufacturer_id
123
126
  string += ' product: %d' % self.product_id
127
+ string += ' functions: %s' % ','.join(self.supported_functions)
124
128
  return string
125
129
 
126
130
  def supports_function(self, name):
@@ -164,3 +168,14 @@ class DeviceId(State):
164
168
  self.aux = None
165
169
  if rsp.auxiliary is not None:
166
170
  self.aux = list(rsp.auxiliary)
171
+
172
+
173
+ class DeviceGuid(State):
174
+ def __str__(self):
175
+ return 'Device GUID: %s' % self.device_guid_string
176
+
177
+ def _from_response(self, rsp):
178
+ self.device_guid = rsp.device_guid
179
+ self.device_guid_string = \
180
+ '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-' \
181
+ '%02x%02x%02x%02x%02x%02x' % tuple(reversed(self.device_guid))
@@ -19,7 +19,7 @@ from enum import Enum
19
19
 
20
20
 
21
21
  from .msgs import create_request_by_name
22
- from .utils import check_completion_code, ByteBuffer
22
+ from .utils import check_completion_code, check_rsp_completion_code, ByteBuffer
23
23
  from .state import State
24
24
 
25
25
  from .msgs.chassis import \
@@ -178,7 +178,7 @@ class Chassis(object):
178
178
  req.set_selector = set_selector
179
179
  req.block_selector = block_selector
180
180
  rsp = self.send_message(req)
181
- check_completion_code(rsp.completion_code)
181
+ check_rsp_completion_code(rsp)
182
182
  return rsp.data
183
183
 
184
184
  def set_system_boot_options(self, parameter_selector, data,
@@ -188,7 +188,7 @@ class Chassis(object):
188
188
  req.parameter_selector.boot_option_parameter_selector = parameter_selector
189
189
  req.data = data
190
190
  rsp = self.send_message(req)
191
- check_completion_code(rsp.completion_code)
191
+ check_rsp_completion_code(rsp)
192
192
 
193
193
  def get_boot_mode(self):
194
194
  """
@@ -30,3 +30,6 @@ ENTITY_ID_PICMG_MICROTCA_CARRIER_HUB = 0xc2
30
30
  ENTITY_ID_PICMG_SHELF_MANAGEMENT_CONTROLLER = 0xf0
31
31
  ENTITY_ID_PICMG_FILTRATION_UNIT = 0xf1
32
32
  ENTITY_ID_PICMG_SHELF_FRU_INFORMATION = 0xf2
33
+ ENTITY_ID_VITA_FRONT_BOARD = 0xa0
34
+ ENTITY_ID_VITA_REAR_TRANSITION_MODULE = 0xc0
35
+ ENTITY_ID_VITA_XMC = 0xc1
@@ -286,7 +286,7 @@ def handle_rmcp_ipmi_msg(context, sdu):
286
286
  ipmi_tx = rmcp.IpmiMsg(context.session)
287
287
  pdu = ipmi_tx.pack(tx_data)
288
288
 
289
- if type(req) == pyipmi.msgs.device_messaging.CloseSessionReq:
289
+ if type(req) is pyipmi.msgs.device_messaging.CloseSessionReq:
290
290
  session.session_id = None
291
291
  session._auth_type = Session.AUTH_TYPE_NONE
292
292
  session._auth_username = None
@@ -14,7 +14,7 @@
14
14
  # License along with this library; if not, write to the Free Software
15
15
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
- from .msgs.constants import COMPLETION_CODE_DESCR
17
+ from .msgs.constants import COMPLETION_CODE_DESCR, CC_ERR_CMD_SPECIFIC_DESC
18
18
 
19
19
 
20
20
  class DecodingError(Exception):
@@ -35,19 +35,26 @@ class IpmiTimeoutError(Exception):
35
35
  class CompletionCodeError(Exception):
36
36
  """IPMI completion code not OK."""
37
37
 
38
- def __init__(self, cc, cmdid=None):
38
+ def __init__(self, cc, cmdid=None, netfn=None, group_extension=None):
39
39
  self.cc = cc
40
- self.cc_desc = self.find_cc_desc(cc)
40
+ self.cc_desc = self.find_cc_desc(cc, cmdid, netfn, group_extension)
41
41
 
42
42
  def __str__(self):
43
43
  return "%s cc=0x%02x desc=%s" \
44
44
  % (self.__class__.__name__, self.cc, self.cc_desc)
45
45
 
46
46
  @staticmethod
47
- def find_cc_desc(error_cc):
47
+ def find_cc_desc(error_cc, cmdid=None, netfn=None, group_extension=None):
48
+ # Search in global completion codes first
48
49
  for cc in COMPLETION_CODE_DESCR:
49
50
  if error_cc == cc[0]:
50
51
  return cc[1]
52
+ # Then search in command specific completion codes
53
+ if cmdid is not None:
54
+ command_cc = CC_ERR_CMD_SPECIFIC_DESC.get((netfn, cmdid, group_extension), {})
55
+ descr = command_cc.get(error_cc, "Unknown error description")
56
+ return descr
57
+ # Completion code description not found
51
58
  return "Unknown error description"
52
59
 
53
60
 
@@ -50,6 +50,18 @@ class VersionField(object):
50
50
  return ''.join("%s.%s" % (self.major, self.minor))
51
51
 
52
52
 
53
+ def _unpack6bitascii(data):
54
+ """Unpack the 6bit ascii encoded string."""
55
+ string = ''
56
+ for i in range(0, len(data), 3):
57
+ d = data[i:i+3]
58
+ string += chr(0x20 + (d[0] & 0x3f))
59
+ string += chr(0x20 + (((d[0] & 0xc0) >> 6) | ((d[1] & 0xf) << 2)))
60
+ string += chr(0x20 + (((d[1] & 0xf0) >> 4) | ((d[2] & 0x3) << 4)))
61
+ string += chr(0x20 + ((d[2] & 0xfc) >> 2))
62
+ return string
63
+
64
+
53
65
  class TypeLengthString(object):
54
66
  """
55
67
  This is the TYPE/LENGTH BYTE FORMAT field represenation according the
@@ -82,12 +94,12 @@ class TypeLengthString(object):
82
94
 
83
95
  self.raw = data[offset+1:offset+1+self.length]
84
96
 
85
- chr_data = ''.join([chr(c) for c in self.raw])
86
97
  if self.field_type == self.TYPE_BCD_PLUS:
87
- self.string = chr_data.decode('bcd+')
98
+ self.string = self.raw.decode('bcd+')
88
99
  elif self.field_type == self.TYPE_6BIT_ASCII:
89
- self.string = chr_data.decode('6bitascii')
100
+ self.string = _unpack6bitascii(self.raw)
90
101
  else:
102
+ chr_data = ''.join([chr(c) for c in self.raw])
91
103
  self.string = chr_data
92
104
 
93
105
 
@@ -31,8 +31,8 @@ def get_sdr_chunk_helper(send_fn, req, reserve_fn, retry=5):
31
31
  if rsp.completion_code == constants.CC_OK:
32
32
  break
33
33
  elif rsp.completion_code == constants.CC_RES_CANCELED:
34
+ time.sleep(1)
34
35
  req.reservation_id = reserve_fn()
35
- time.sleep(0.1)
36
36
  continue
37
37
  elif rsp.completion_code == constants.CC_TIMEOUT:
38
38
  time.sleep(0.1)
@@ -139,7 +139,9 @@ class Hpm(object):
139
139
  raise HpmError('initiate_upgrade_action CC=0x%02x' % e.cc)
140
140
 
141
141
  def upload_firmware_block(self, block_number, data):
142
- data = [ord(c) for c in data]
142
+ if isinstance(data, str):
143
+ data = [ord(c) for c in data]
144
+
143
145
  self.send_message_with_name('UploadFirmwareBlock', number=block_number,
144
146
  data=data)
145
147
 
@@ -37,7 +37,8 @@ class Aardvark(object):
37
37
  NAME = 'aardvark'
38
38
 
39
39
  def __init__(self, slave_address=0x20, port=0, serial_number=None,
40
- enable_i2c_pullups=None, enable_target_power=None):
40
+ enable_i2c_pullups=None, enable_target_power=None,
41
+ enable_fastmode=None):
41
42
  if pyaardvark is None:
42
43
  raise RuntimeError('No pyaardvark module found. You can not '
43
44
  'use this interface.')
@@ -55,12 +56,23 @@ class Aardvark(object):
55
56
  if enable_target_power:
56
57
  self.enable_target_power(enable_target_power)
57
58
 
59
+ if enable_fastmode is not None:
60
+ self.enable_fastmode(enable_fastmode)
61
+ else:
62
+ self.enable_fastmode(False)
63
+
58
64
  def enable_pullups(self, enabled):
59
65
  self._dev.i2c_pullups = enabled
60
66
 
61
67
  def enable_target_power(self, enabled):
62
68
  self._dev.target_power = enabled
63
69
 
70
+ def enable_fastmode(self, enabled):
71
+ if enabled:
72
+ self._dev.i2c_bitrate = 400
73
+ else:
74
+ self._dev.i2c_bitrate = 100
75
+
64
76
  def raw_write(self, address, data):
65
77
  self._dev.i2c_master_write(address, data)
66
78
 
@@ -45,10 +45,6 @@ class IpmbHeader(object):
45
45
  *-------*--------------*----------*-------*---------------*-------*
46
46
  """
47
47
 
48
- def __init__(self, data=None):
49
- if data:
50
- self.decode(data)
51
-
52
48
  rs_sa = None
53
49
  rs_lun = None
54
50
  rq_sa = None
@@ -58,6 +54,17 @@ class IpmbHeader(object):
58
54
  cmdid = None
59
55
  checksum = None
60
56
 
57
+ def __init__(self, data=None):
58
+ if data:
59
+ self.decode(data)
60
+
61
+ def __str__(self):
62
+ return f'rs_sa=0x{self.rs_sa:02x}, rs_lun={self.rs_lun}, ' \
63
+ f'rq_sa=0x{self.rq_sa:02x}, rq_lun={self.rq_lun}, ' \
64
+ f'rq_seq={self.rq_seq}, ' \
65
+ f'netfn=0x{self.netfn:02x}, ' \
66
+ f'cmdid=0x{self.cmdid:02x}'
67
+
61
68
 
62
69
  class IpmbHeaderReq(IpmbHeader):
63
70
  """Representation of the IPMI request message header."""
@@ -214,7 +221,8 @@ def decode_bridged_message(rx_data):
214
221
  return rx_data
215
222
 
216
223
 
217
- def rx_filter(header, data):
224
+ def rx_filter(header, data, rq_sa=False, rs_sa=False, rq_lun=False,
225
+ rs_lun=True, rq_seq=True):
218
226
  """Check if the message in rx_data matches to the information in header.
219
227
 
220
228
  The following checks are done:
@@ -234,15 +242,26 @@ def rx_filter(header, data):
234
242
  checks = [
235
243
  (checksum(data[0:3]), 0, 'Header checksum failed'),
236
244
  (checksum(data[3:]), 0, 'payload checksum failed'),
237
- # rsp_header.rq_sa, header.rq_sa, 'slave address mismatch'),
238
245
  (rsp_header.netfn, header.netfn | 1, 'NetFn mismatch'),
239
- # rsp_header.rs_sa, header.rs_sa, 'target address mismatch'),
240
- # rsp_header.rq_lun, header.rq_lun, 'request LUN mismatch'),
241
- (rsp_header.rs_lun, header.rs_lun, 'responder LUN mismatch'),
242
- (rsp_header.rq_seq, header.rq_seq, 'sequence number mismatch'),
243
246
  (rsp_header.cmdid, header.cmdid, 'command id mismatch'),
244
247
  ]
245
248
 
249
+ # optional checks
250
+ if rq_sa:
251
+ checks.append((rsp_header.rq_sa, header.rq_sa, 'slave address mismatch'))
252
+
253
+ if rs_sa:
254
+ checks.append((rsp_header.rs_sa, header.rs_sa, 'target address mismatch'))
255
+
256
+ if rq_lun:
257
+ checks.append((rsp_header.rq_lun, header.rq_lun, 'request LUN mismatch'))
258
+
259
+ if rs_lun:
260
+ checks.append((rsp_header.rs_lun, header.rs_lun, 'responder LUN mismatch'))
261
+
262
+ if rq_seq:
263
+ checks.append((rsp_header.rq_seq, header.rq_seq, 'sequence number mismatch'))
264
+
246
265
  match = True
247
266
 
248
267
  for left, right, msg in checks:
@@ -42,12 +42,17 @@ class Ipmitool(object):
42
42
  IPMITOOL_PATH = 'ipmitool'
43
43
  supported_interfaces = ['lan', 'lanplus', 'serial-terminal', 'open']
44
44
 
45
- def __init__(self, interface_type='lan'):
45
+ def __init__(self, interface_type='lan', cipher=None):
46
46
  if interface_type in self.supported_interfaces:
47
47
  self._interface_type = interface_type
48
48
  else:
49
49
  raise RuntimeError('interface type %s not supported' %
50
50
  interface_type)
51
+ if cipher is not None and int(cipher) not in range(0, 255):
52
+ raise RuntimeError('cipher %s not in allowed range [0-255]' %
53
+ cipher)
54
+ else:
55
+ self._cipher = cipher
51
56
 
52
57
  self.re_completion_code = re.compile(
53
58
  r"Unable to send RAW command \(.*rsp=(0x[0-9a-f]+)\)")
@@ -213,6 +218,15 @@ class Ipmitool(object):
213
218
 
214
219
  return cmd
215
220
 
221
+ def _build_ipmitool_priv_level(self, level):
222
+ LEVELS = {
223
+ Session.PRIV_LEVEL_USER: 'USER',
224
+ Session.PRIV_LEVEL_OPERATOR: 'OPERATOR',
225
+ Session.PRIV_LEVEL_ADMINISTRATOR: 'ADMINISTRATOR'
226
+ }
227
+
228
+ return (' -L %s' % LEVELS[level])
229
+
216
230
  def _build_ipmitool_cmd(self, target, lun, netfn, raw_bytes):
217
231
  if not hasattr(self, '_session'):
218
232
  raise RuntimeError('Session needs to be set')
@@ -222,6 +236,10 @@ class Ipmitool(object):
222
236
  cmd += (' -H %s' % self._session.rmcp_host)
223
237
  cmd += (' -p %s' % self._session.rmcp_port)
224
238
 
239
+ cmd += self._build_ipmitool_priv_level(self._session.priv_level)
240
+
241
+ if self._cipher:
242
+ cmd += (' -C %s' % self._cipher)
225
243
  if self._session.auth_type == Session.AUTH_TYPE_NONE:
226
244
  cmd += ' -P ""'
227
245
  elif self._session.auth_type == Session.AUTH_TYPE_PASSWORD:
@@ -27,12 +27,13 @@ from ..session import Session
27
27
  from ..msgs import (create_message, create_request_by_name,
28
28
  encode_message, decode_message, constants)
29
29
  from ..messaging import ChannelAuthenticationCapabilities
30
- from ..errors import DecodingError, NotSupportedError
30
+ from ..errors import DecodingError, NotSupportedError, RetryError
31
31
  from ..logger import log
32
32
  from ..interfaces.ipmb import (IpmbHeaderReq, encode_ipmb_msg,
33
33
  encode_bridged_message, decode_bridged_message,
34
34
  rx_filter)
35
- from ..utils import check_completion_code, py3_array_tobytes
35
+ from ..utils import (check_completion_code, check_rsp_completion_code,
36
+ py3_array_tobytes)
36
37
 
37
38
 
38
39
  CLASS_NORMAL_MSG = 0x00
@@ -227,8 +228,9 @@ class IpmiMsg(object):
227
228
  HEADER_FORMAT_NO_AUTH = '!BIIB'
228
229
  HEADER_FORMAT_AUTH = '!BII16BB'
229
230
 
230
- def __init__(self, session=None):
231
+ def __init__(self, session=None, ignore_sdu_length=False):
231
232
  self.session = session
233
+ self.ignore_sdu_length = ignore_sdu_length
232
234
 
233
235
  def _pack_session_id(self):
234
236
  if self.session is not None:
@@ -321,19 +323,28 @@ class IpmiMsg(object):
321
323
  (self.auth_type, self.sequence_number, self.session_id,
322
324
  data_len) = struct.unpack(self.HEADER_FORMAT_NO_AUTH, header)
323
325
 
324
- if len(pdu) < header_len + data_len:
325
- raise DecodingError('short SDU')
326
- elif len(pdu) > header_len + data_len:
327
- raise DecodingError('SDU has extra bytes ({:d},{:d},{:d} )'.format(
328
- len(pdu), header_len, data_len))
326
+ if not self.ignore_sdu_length:
327
+ if len(pdu) < header_len + data_len:
328
+ raise DecodingError('short SDU')
329
+ elif len(pdu) > header_len + data_len:
330
+ raise DecodingError(
331
+ 'SDU has extra bytes ({:d},{:d},{:d} )'.format(
332
+ len(pdu), header_len, data_len))
329
333
 
330
334
  if hasattr(self, 'check_header'):
331
335
  self.check_header()
332
336
 
333
- if data_len != 0:
334
- sdu = pdu[header_len:header_len + data_len]
337
+ if not self.ignore_sdu_length:
338
+ if data_len != 0:
339
+ sdu = pdu[header_len:header_len + data_len]
340
+ else:
341
+ sdu = None
335
342
  else:
336
- sdu = None
343
+ try:
344
+ sdu = pdu[header_len:]
345
+ sdu = None if sdu == b'' else sdu
346
+ except IndexError:
347
+ sdu = None
337
348
 
338
349
  return sdu
339
350
 
@@ -350,19 +361,52 @@ class Rmcp(object):
350
361
  _session = None
351
362
 
352
363
  def __init__(self, slave_address=0x81, host_target_address=0x20,
353
- keep_alive_interval=1):
364
+ keep_alive_interval=1, max_retries=0, quirks_cfg=dict()):
365
+ """Native RMCP interface constructor
366
+
367
+ Parameter `quirks_cfg`: a dict of additional configuration parameters
368
+ for the RMCP object. Supported keys/values are :
369
+
370
+ - `rmcp_ignore_sdu_length: bool` whether or not to verify the SDU length
371
+ of a received IPMI message. If absent or `False` (default), an exception
372
+ will be raised if there is a mismatch between the received SDU length
373
+ and the value stored in `payload_length` field of the PDU header. If
374
+ `True`, ignore the header field `payload_length` and unpack the PDU
375
+ anyway.
376
+
377
+ Example:
378
+
379
+ interfaces.create_interface(
380
+ interface="rmcp",
381
+ quirks_cfg={'rmcp_ignore_sdu_length': True}
382
+ )
383
+
384
+ - `rmcp_ignore_rq_seq: bool` wheater or not to verify the req_seq field
385
+ for rx_filter. The default value is `False`.
386
+
387
+ Example:
388
+
389
+ interfaces.create_interface(
390
+ interface="rmcp",
391
+ quirks_cfg={'rmcp_ignore_rq_seq': True}
392
+ )
393
+ """
354
394
  self.host = None
355
395
  self.port = None
356
396
  self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
357
397
  self.seq_number = 0xff
358
398
  self.slave_address = slave_address
359
399
  self.host_target = Target(host_target_address)
400
+ self.max_retries = max_retries
360
401
  self.set_timeout(2.0)
361
402
  self.next_sequence_number = 0
362
403
  self.keep_alive_interval = keep_alive_interval
363
404
  self._stop_keep_alive = None
364
405
  self._q = Queue()
365
406
  self.transaction_lock = threading.Lock()
407
+ self.quirks_cfg = quirks_cfg
408
+ self.ignore_sdu_length = quirks_cfg.get('rmcp_ignore_sdu_length', False)
409
+ self.ignore_rq_seq = quirks_cfg.get('rmcp_ignore_rq_seq', False)
366
410
 
367
411
  def _send_rmcp_msg(self, sdu, class_of_msg):
368
412
  rmcp = RmcpMsg(class_of_msg)
@@ -387,11 +431,11 @@ class Rmcp(object):
387
431
  tx_data = ipmi.pack(data)
388
432
  self._send_rmcp_msg(tx_data, RMCP_CLASS_IPMI)
389
433
 
390
- def _receive_ipmi_msg(self):
434
+ def _receive_ipmi_msg(self, ignore_sdu_length=False):
391
435
  (_, class_of_msg, pdu) = self._receive_rmcp_msg()
392
436
  if class_of_msg != RMCP_CLASS_IPMI:
393
437
  raise DecodingError('invalid class field in ASF message')
394
- msg = IpmiMsg()
438
+ msg = IpmiMsg(ignore_sdu_length=ignore_sdu_length)
395
439
  data = msg.unpack(pdu)
396
440
  log().debug('IPMI RX: {:s}'.format(
397
441
  ' '.join('%02x' % b for b in array('B', data))))
@@ -415,13 +459,13 @@ class Rmcp(object):
415
459
  self._send_asf_msg(ping)
416
460
  self._receive_asf_msg(AsfPong)
417
461
 
418
- def _get_channel_auth_cap(self):
462
+ def _get_channel_auth_cap(self, session):
419
463
  CHANNEL_NUMBER_FOR_THIS = 0xe
420
464
  # get channel auth cap
421
465
  req = create_request_by_name('GetChannelAuthenticationCapabilities')
422
466
  req.target = self.host_target
423
467
  req.channel.number = CHANNEL_NUMBER_FOR_THIS
424
- req.privilege_level.requested = Session.PRIV_LEVEL_ADMINISTRATOR
468
+ req.privilege_level.requested = session.priv_level
425
469
  rsp = self.send_and_receive(req)
426
470
  check_completion_code(rsp.completion_code)
427
471
  caps = ChannelAuthenticationCapabilities(rsp)
@@ -435,7 +479,7 @@ class Rmcp(object):
435
479
  if session._auth_username:
436
480
  req.user_name = session._auth_username.ljust(16, '\x00')
437
481
  rsp = self.send_and_receive(req)
438
- check_completion_code(rsp.completion_code)
482
+ check_rsp_completion_code(rsp)
439
483
  return rsp
440
484
 
441
485
  def _activate_session(self, session, challenge):
@@ -443,13 +487,12 @@ class Rmcp(object):
443
487
  req = create_request_by_name('ActivateSession')
444
488
  req.target = self.host_target
445
489
  req.authentication.type = session.auth_type
446
- req.privilege_level.maximum_requested =\
447
- Session.PRIV_LEVEL_ADMINISTRATOR
490
+ req.privilege_level.maximum_requested = session.priv_level
448
491
  req.challenge_string = challenge
449
492
  req.session_id = self._session.sid
450
493
  req.initial_outbound_sequence_number = random.randrange(1, 0xffffffff)
451
494
  rsp = self.send_and_receive(req)
452
- check_completion_code(rsp.completion_code)
495
+ check_rsp_completion_code(rsp)
453
496
  return rsp
454
497
 
455
498
  def _set_session_privilege_level(self, level):
@@ -457,7 +500,7 @@ class Rmcp(object):
457
500
  req.target = self.host_target
458
501
  req.privilege_level.requested = level
459
502
  rsp = self.send_and_receive(req)
460
- check_completion_code(rsp.completion_code)
503
+ check_rsp_completion_code(rsp)
461
504
  return rsp
462
505
 
463
506
  def _get_device_id(self):
@@ -476,7 +519,7 @@ class Rmcp(object):
476
519
 
477
520
  # 1 - Get Channel Authentication Capabilities
478
521
  log().debug('Get Channel Authentication Capabilities')
479
- caps = self._get_channel_auth_cap()
522
+ caps = self._get_channel_auth_cap(session)
480
523
  log().debug('%s' % caps)
481
524
 
482
525
  # 2 - Get Session Challenge
@@ -485,6 +528,7 @@ class Rmcp(object):
485
528
  rsp = self._get_session_challenge(session)
486
529
  session_challenge = rsp.challenge_string
487
530
  session.sid = rsp.temporary_session_id
531
+
488
532
  self._session = session
489
533
 
490
534
  # 3 - Activate Session
@@ -496,7 +540,7 @@ class Rmcp(object):
496
540
 
497
541
  log().debug('Set Session Privilege Level')
498
542
  # 4 - Set Session Privilege Level
499
- self._set_session_privilege_level(Session.PRIV_LEVEL_ADMINISTRATOR)
543
+ self._set_session_privilege_level(session.priv_level)
500
544
 
501
545
  log().debug('Session opened')
502
546
 
@@ -555,25 +599,47 @@ class Rmcp(object):
555
599
  tx_data = encode_ipmb_msg(header, payload)
556
600
 
557
601
  with self.transaction_lock:
558
- self._send_ipmi_msg(tx_data)
559
-
560
- received = False
561
- while received is False:
562
- if not self._q.empty():
563
- rx_data = self._q.get()
564
- else:
565
- rx_data = self._receive_ipmi_msg()
566
-
567
- if array('B', rx_data)[5] == constants.CMDID_SEND_MESSAGE:
568
- rx_data = decode_bridged_message(rx_data)
569
- if not rx_data:
570
- # the forwarded reply is expected in the next packet
571
- continue
572
-
573
- received = rx_filter(header, rx_data)
574
-
575
- if not received:
576
- self._q.put(rx_data)
602
+ retry = 0
603
+ while retry <= self.max_retries:
604
+ try:
605
+ self._send_ipmi_msg(tx_data)
606
+
607
+ received = False
608
+ received_retry = 0
609
+ while received is False and received_retry <= self.max_retries:
610
+ if not self._q.empty():
611
+ rx_data = self._q.get()
612
+ else:
613
+ rx_data = self._receive_ipmi_msg(self.ignore_sdu_length)
614
+
615
+ if array('B', rx_data)[5] == constants.CMDID_SEND_MESSAGE:
616
+ rx_data = decode_bridged_message(rx_data)
617
+ if not rx_data:
618
+ # the forwarded reply is expected in the next packet
619
+ # so we do not increment the retry counter as
620
+ # it's not really a retry
621
+ continue
622
+
623
+ received = rx_filter(header, rx_data,
624
+ rq_seq=not self.ignore_rq_seq)
625
+
626
+ if not received:
627
+ self._q.put(rx_data)
628
+
629
+ received_retry += 1
630
+
631
+ if not received:
632
+ raise RetryError("Max retry while checking received"
633
+ "data against request header for rmcp"
634
+ f"host {self.host}")
635
+ break
636
+
637
+ except socket.timeout:
638
+ retry += 1
639
+
640
+ if retry > self.max_retries:
641
+ raise RetryError("Max retry while sending and/or receiving ipmi"
642
+ f"message for rmcp host {self.host}")
577
643
 
578
644
  return rx_data[6:-1]
579
645