python-ipmi 0.5.4__py3-none-any.whl → 0.5.7__py3-none-any.whl
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.
- pyipmi/__init__.py +2 -2
- pyipmi/bmc.py +15 -0
- pyipmi/chassis.py +3 -3
- pyipmi/constants.py +3 -0
- pyipmi/emulation.py +1 -1
- pyipmi/errors.py +11 -4
- pyipmi/fields.py +15 -3
- pyipmi/helper.py +1 -1
- pyipmi/hpm.py +3 -1
- pyipmi/interfaces/aardvark.py +13 -1
- pyipmi/interfaces/ipmb.py +29 -10
- pyipmi/interfaces/ipmitool.py +19 -1
- pyipmi/interfaces/rmcp.py +108 -42
- pyipmi/ipmitool.py +41 -6
- pyipmi/lan.py +10 -3
- pyipmi/messaging.py +4 -3
- pyipmi/msgs/bmc.py +6 -7
- pyipmi/msgs/chassis.py +0 -1
- pyipmi/msgs/constants.py +51 -4
- pyipmi/msgs/dcmi.py +0 -2
- pyipmi/msgs/device_messaging.py +3 -6
- pyipmi/msgs/event.py +0 -1
- pyipmi/msgs/fru.py +0 -1
- pyipmi/msgs/hpm.py +0 -1
- pyipmi/msgs/lan.py +0 -1
- pyipmi/msgs/message.py +1 -4
- pyipmi/msgs/picmg.py +0 -2
- pyipmi/msgs/registry.py +0 -1
- pyipmi/msgs/sdr.py +0 -1
- pyipmi/msgs/sel.py +0 -1
- pyipmi/msgs/sensor.py +0 -1
- pyipmi/msgs/vita.py +543 -8
- pyipmi/sdr.py +4 -3
- pyipmi/sensor.py +10 -0
- pyipmi/session.py +13 -0
- pyipmi/utils.py +19 -0
- pyipmi/version.py +1 -1
- {python_ipmi-0.5.4.dist-info → python_ipmi-0.5.7.dist-info}/METADATA +6 -4
- python_ipmi-0.5.7.dist-info/RECORD +56 -0
- {python_ipmi-0.5.4.dist-info → python_ipmi-0.5.7.dist-info}/WHEEL +1 -1
- python_ipmi-0.5.4.dist-info/RECORD +0 -56
- {python_ipmi-0.5.4.dist-info → python_ipmi-0.5.7.dist-info}/AUTHORS +0 -0
- {python_ipmi-0.5.4.dist-info → python_ipmi-0.5.7.dist-info}/COPYING +0 -0
- {python_ipmi-0.5.4.dist-info → python_ipmi-0.5.7.dist-info}/entry_points.txt +0 -0
- {python_ipmi-0.5.4.dist-info → python_ipmi-0.5.7.dist-info}/top_level.txt +0 -0
pyipmi/__init__.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
213
|
+
check_rsp_completion_code(rsp)
|
|
214
214
|
return rsp
|
|
215
215
|
|
|
216
216
|
def raw_command(self, lun, netfn, raw_bytes):
|
pyipmi/bmc.py
CHANGED
|
@@ -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))
|
pyipmi/chassis.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
191
|
+
check_rsp_completion_code(rsp)
|
|
192
192
|
|
|
193
193
|
def get_boot_mode(self):
|
|
194
194
|
"""
|
pyipmi/constants.py
CHANGED
|
@@ -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
|
pyipmi/emulation.py
CHANGED
|
@@ -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)
|
|
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
|
pyipmi/errors.py
CHANGED
|
@@ -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
|
|
pyipmi/fields.py
CHANGED
|
@@ -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 =
|
|
98
|
+
self.string = self.raw.decode('bcd+')
|
|
88
99
|
elif self.field_type == self.TYPE_6BIT_ASCII:
|
|
89
|
-
self.string =
|
|
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
|
|
pyipmi/helper.py
CHANGED
|
@@ -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)
|
pyipmi/hpm.py
CHANGED
|
@@ -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
|
|
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
|
|
pyipmi/interfaces/aardvark.py
CHANGED
|
@@ -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
|
|
pyipmi/interfaces/ipmb.py
CHANGED
|
@@ -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:
|
pyipmi/interfaces/ipmitool.py
CHANGED
|
@@ -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:
|
pyipmi/interfaces/rmcp.py
CHANGED
|
@@ -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,
|
|
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
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
334
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
|