kontron / python-ipmi

A pure python IPMI library
GNU Lesser General Public License v2.1
185 stars 74 forks source link

Top-level exception is raised when the total length of an IPMI pdu exceed the value of its payload_length attribute #140

Closed canteuni closed 1 year ago

canteuni commented 1 year ago

This issue is more a question than a bug. It concerns the following code in rmcp.py : https://github.com/kontron/python-ipmi/blob/63079a6937f510dbbb2e0ab4ca524f98ef7abb1e/pyipmi/interfaces/rmcp.py#L326-L328

TL;DR

Is it acceptable to return the response data when using native RMCP interface even if the received data length do not match the payload length specified in the response header ? (Rather than raising a top-level exception)

I can propose something like a FeatureFlag CheckIPMIPayloadLength that is True by default, but if False it disables the verification of the payload.

Problem

My problem is that I have a specific BMC that supports the IPMI Quanta OEM command NetFn=0x30, Cmd=0x49. This command is used to retrieve the last POST codes of the server (self-test codes that happened before the bios). It can returns up to the last 256 POST codes.

The real bug is with the actual implementation of this OEM command on the BMC side : in IPMI v1.5, the field payload length of an IPMI response header is coded on 1 byte and is 1-based. This means an IPMI response payload total length cannot exceed 256 bytes. But an IPMI payload/message contains more than just the payload data : it has also rqAddr, netFn, checksum, cmd, completion code etc. which adds always 8 bytes to the actual response data.

In my case, if the Quanta OEM GetPostCodes command wants to return 256 POST codes, the total payload/message length will be 256+8 = 264 bytes, which can't be coded in the field payload length of the response header. This results in python-ipmi raising the following exception when performing a raw command :

pyipmi.errors.DecodingError: SDU has extra bytes (290,26,8 )

The 26 is the length of the IPMI header. The 290 is the total length of received data, header length + received payload length = 264 + 26 = 290. The 8 is the payload length field in the header, I think it comes from the hex representation of 264: 01 08 but because this field is coded on 1 byte, it only got the 08 part of the hex representation.

If I try the same GetPostCodes raw command with a server that only sends back 128 POST codes for example, everything works fine.

Current behavior

int = interfaces.create_interface(
            interface='rmcp',
            slave_address=0x81,
            host_target_address=0x20,
            keep_alive_interval=0
)
ipmi = pyipmi.create_connection(int)
ipmi.session.set_session_type_rmcp(...)
ipmi.session.set_auth_type_user(...)
ipmi.target = ...
ipmi.session.establish()
res = ipmi.interface.send_and_receive_raw(ipmi.target, 0x00, 0x30, b'\x49\x01')
res = array('B', res)
if res[0] != 0:
    # Completion code error
    raise Exception(f"Wrong completion code: {res[0]}")

print([f"{i:02x}" for i in res.tolist()])

> pyipmi.errors.DecodingError: SDU has extra bytes (290,26,8 )

Expected behavior

int = interfaces.create_interface(
            interface='rmcp',
            slave_address=0x81,
            host_target_address=0x20,
            keep_alive_interval=0
)
ipmi = pyipmi.create_connection(int)
ipmi.session.set_session_type_rmcp(...)
ipmi.session.set_auth_type_user(...)
ipmi.target = ...
ipmi.session.establish()
res = ipmi.interface.send_and_receive_raw(ipmi.target, 0x00, 0x30, b'\x49\x01')
res = array('B', res)
if res[0] != 0:
    # Completion code error
    raise Exception(f"Wrong completion code: {res[0]}")

print([f"{i:02x}" for i in res.tolist()])

> ["00", "A0", "B2", "99", ... , "EC", "FF", "9C"]  # list of length 257 (256 POST codes + completion code)

Questions