├── .gitignore ├── subprojects ├── googletest.wrap ├── CLI11.wrap ├── sdbusplus.wrap ├── phosphor-logging.wrap ├── phosphor-dbus-interfaces.wrap └── phosphor-host-ipmid.wrap ├── .clang-tidy ├── phosphor-ipmi-net@.socket ├── sol_module.hpp ├── README.md ├── prng.hpp ├── meson.options ├── main.hpp ├── phosphor-ipmi-net@.service ├── command ├── guid.hpp ├── rakp34.hpp ├── sol_cmds.hpp ├── sol_cmds.cpp ├── rakp12.hpp ├── session_cmds.hpp ├── open_session.cpp ├── open_session.hpp ├── channel_auth.hpp ├── guid.cpp ├── rakp34.cpp ├── payload_cmds.cpp ├── channel_auth.cpp └── payload_cmds.hpp ├── test └── meson.build ├── rmcp.hpp ├── comm_module.hpp ├── OWNERS ├── endian.hpp ├── sol_module.cpp ├── sol └── console_buffer.hpp ├── comm_module.cpp ├── auth_algo.cpp ├── meson.build ├── main.cpp ├── sd_event_loop.hpp ├── .clang-format ├── integrity_algo.cpp ├── message_handler.hpp ├── sessions_manager.hpp ├── command_table.cpp ├── crypt_algo.hpp ├── message_handler.cpp ├── auth_algo.hpp ├── crypt_algo.cpp ├── command_table.hpp ├── sd_event_loop.cpp ├── message.hpp ├── socket_channel.hpp ├── message_parsers.hpp └── session.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | /build*/ 2 | /subprojects/* 3 | !subprojects/*.wrap 4 | -------------------------------------------------------------------------------- /subprojects/googletest.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/google/googletest.git 3 | revision = HEAD 4 | -------------------------------------------------------------------------------- /subprojects/CLI11.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/CLIUtils/CLI11.git 3 | revision = HEAD 4 | 5 | [provide] 6 | CLI11 = CLI11_dep 7 | -------------------------------------------------------------------------------- /subprojects/sdbusplus.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/openbmc/sdbusplus.git 3 | revision = HEAD 4 | 5 | [provide] 6 | sdbusplus = sdbusplus_dep 7 | -------------------------------------------------------------------------------- /subprojects/phosphor-logging.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/openbmc/phosphor-logging.git 3 | revision = HEAD 4 | 5 | [provide] 6 | phosphor-logging = phosphor_logging_dep 7 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '-*, 2 | bugprone-unchecked-optional-access, 3 | readability-identifier-naming 4 | ' 5 | 6 | HeaderFilterRegex: (?!^subprojects).* 7 | 8 | WarningsAsErrors: '*' 9 | -------------------------------------------------------------------------------- /phosphor-ipmi-net@.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | ConditionPathExists=/sys/class/net/%i 3 | 4 | [Socket] 5 | ListenDatagram=623 6 | BindToDevice=%i 7 | 8 | [Install] 9 | WantedBy=sockets.target 10 | 11 | -------------------------------------------------------------------------------- /subprojects/phosphor-dbus-interfaces.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/openbmc/phosphor-dbus-interfaces.git 3 | revision = HEAD 4 | 5 | [provide] 6 | phosphor-dbus-interfaces = phosphor_dbus_interfaces_dep 7 | -------------------------------------------------------------------------------- /subprojects/phosphor-host-ipmid.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/openbmc/phosphor-host-ipmid.git 3 | revision = HEAD 4 | 5 | [provide] 6 | libipmid = ipmid_dep 7 | libchannellayer = channellayer_dep 8 | libuserlayer = userlayer_dep 9 | -------------------------------------------------------------------------------- /sol_module.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace sol 4 | { 5 | 6 | namespace command 7 | { 8 | 9 | /** @brief Register SOL commands to the Command Table */ 10 | void registerCommands(); 11 | 12 | } // namespace command 13 | 14 | } // namespace sol 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phosphor-net-ipmid 2 | 3 | ## To Build 4 | 5 | To build this package, do the following steps: 6 | 7 | ```sh 8 | 1. ./bootstrap.sh 9 | 2. ./configure ${CONFIGURE_FLAGS} 10 | 3. make 11 | ``` 12 | 13 | To clean the repository run `./bootstrap.sh clean`. 14 | -------------------------------------------------------------------------------- /prng.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace crypto 4 | { 5 | 6 | struct prng 7 | { 8 | static unsigned int rand() 9 | { 10 | unsigned int v; 11 | RAND_bytes(reinterpret_cast(&v), sizeof(v)); 12 | return v; 13 | } 14 | }; 15 | 16 | } // namespace crypto 17 | -------------------------------------------------------------------------------- /meson.options: -------------------------------------------------------------------------------- 1 | option('tests', type: 'feature', value: 'enabled', description: 'Build tests') 2 | 3 | option( 4 | 'rmcp_ping', 5 | type: 'feature', 6 | value: 'enabled', 7 | description: 'Enable RMCP Ping support', 8 | ) 9 | 10 | option( 11 | 'pam_authenticate', 12 | type: 'feature', 13 | value: 'enabled', 14 | description: 'Enable Pam Authenticate', 15 | ) 16 | -------------------------------------------------------------------------------- /main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // Select call timeout is set arbitrarily set to 30 sec 10 | static constexpr size_t SELECT_CALL_TIMEOUT = 30; 11 | static const auto IPMI_STD_PORT = 623; 12 | 13 | extern sd_bus* bus; 14 | 15 | std::shared_ptr getSdBus(); 16 | std::shared_ptr getIo(); 17 | -------------------------------------------------------------------------------- /phosphor-ipmi-net@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Network IPMI daemon 3 | After=phosphor-ipmi-host.service 4 | Requires=sys-subsystem-net-devices-%i.device 5 | After=sys-subsystem-net-devices-%i.device 6 | ConditionPathExists=/sys/class/net/%i 7 | 8 | [Service] 9 | ExecStart=/usr/bin/netipmid -c %i 10 | SyslogIdentifier=netipmid-%i 11 | Restart=always 12 | RuntimeDirectory = ipmi 13 | RuntimeDirectoryPreserve = yes 14 | StateDirectory = ipmi 15 | 16 | [Install] 17 | DefaultInstance=eth0 18 | WantedBy=multi-user.target 19 | RequiredBy= 20 | -------------------------------------------------------------------------------- /command/guid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "comm_module.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace command 11 | { 12 | 13 | constexpr size_t BMC_GUID_LEN = 16; 14 | 15 | using Guid = std::array; 16 | 17 | /** 18 | * @brief Get System GUID 19 | * 20 | * @return If UUID is successfully read from the Chassis DBUS object, then the 21 | * GUID is returned, else a canned GUID is returned 22 | */ 23 | const Guid& getSystemGUID(); 24 | 25 | /** 26 | * @brief Register the callback to update the cache when the GUID changes 27 | */ 28 | void registerGUIDChangeCallback(); 29 | 30 | } // namespace command 31 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | gtest_dep = dependency('gtest', main: true, disabler: true, required: false) 2 | gmock_dep = dependency('gmock', disabler: true, required: false) 3 | if not gtest_dep.found() or not gmock_dep.found() 4 | gtest_proj = import('cmake').subproject('googletest', required: false) 5 | if gtest_proj.found() 6 | gtest_dep = declare_dependency( 7 | dependencies: [ 8 | dependency('threads'), 9 | gtest_proj.dependency('gtest'), 10 | gtest_proj.dependency('gtest_main'), 11 | ], 12 | ) 13 | gmock_dep = gtest_proj.dependency('gmock') 14 | else 15 | assert( 16 | not get_option('tests').enabled(), 17 | 'Googletest is required if tests are enabled', 18 | ) 19 | endif 20 | endif 21 | 22 | test_sources = ['../integrity_algo.cpp', '../crypt_algo.cpp'] 23 | 24 | tests = ['cipher.cpp'] 25 | 26 | foreach t : tests 27 | test( 28 | t, 29 | executable( 30 | t.underscorify(), 31 | t, 32 | test_sources, 33 | include_directories: ['..'], 34 | dependencies: [gtest_dep, gmock_dep, libcrypto_dep], 35 | ), 36 | workdir: meson.current_source_dir(), 37 | ) 38 | endforeach 39 | -------------------------------------------------------------------------------- /rmcp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace rmcp 8 | { 9 | 10 | /* 11 | * RSP needs more keying material than can be provided by session 12 | * integrity key alone. As a result all keying material for the RSP 13 | * confidentiality algorithms will be generated by processing a 14 | * pre-defined set of constants using HMAC per [RFC2104], keyed by SIK. 15 | * These constants are constructed using a hexadecimal octet value 16 | * repeated up to the HMAC block size in length starting with the 17 | * constant 01h. This mechanism can be used to derive up to 255 18 | * HMAC-block-length pieces of keying material from a single SIK.For the 19 | * mandatory confidentiality algorithm AES-CBC-128, processing the 20 | * following constant will generate the required amount of keying 21 | * material. 22 | */ 23 | constexpr size_t CONST_N_SIZE = 20; 24 | using Const_n = std::array; 25 | 26 | static constexpr Const_n const_1 = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 27 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 28 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; 29 | 30 | static constexpr Const_n const_2 = {0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 31 | 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 32 | 0x02, 0x02, 0x02, 0x02, 0x02, 0x02}; 33 | 34 | } // namespace rmcp 35 | -------------------------------------------------------------------------------- /command/rakp34.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "comm_module.hpp" 4 | #include "message_handler.hpp" 5 | 6 | #include 7 | 8 | namespace command 9 | { 10 | 11 | /** 12 | * @struct RAKP3request 13 | * 14 | * IPMI Payload for RAKP Message 3 15 | */ 16 | struct RAKP3request 17 | { 18 | uint8_t messageTag; 19 | uint8_t rmcpStatusCode; 20 | uint16_t reserved; 21 | uint32_t managedSystemSessionID; 22 | } __attribute__((packed)); 23 | 24 | /** 25 | * @struct RAKP4response 26 | * 27 | * IPMI Payload for RAKP Message 4 28 | */ 29 | struct RAKP4response 30 | { 31 | uint8_t messageTag; 32 | uint8_t rmcpStatusCode; 33 | uint16_t reserved; 34 | uint32_t remoteConsoleSessionID; 35 | } __attribute__((packed)); 36 | 37 | /** 38 | * @brief RAKP Message 3, RAKP Message 4 39 | * 40 | * The session activation process is completed by the remote console and BMC 41 | * exchanging messages that are signed according to the Authentication Algorithm 42 | * that was negotiated, and the parameters that were passed in the earlier 43 | * messages. RAKP Message 3 is the signed message from the remote console to the 44 | * BMC. After receiving RAKP Message 3, the BMC returns RAKP Message 4 - a 45 | * signed message from BMC to the remote console. 46 | * 47 | * @param[in] inPayload - Request Data for the command 48 | * @param[in] handler - Reference to the Message Handler 49 | * 50 | * @return Response data for the command 51 | */ 52 | std::vector RAKP34(const std::vector& inPayload, 53 | std::shared_ptr& handler); 54 | 55 | } // namespace command 56 | -------------------------------------------------------------------------------- /comm_module.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message_handler.hpp" 4 | 5 | #include 6 | 7 | namespace command 8 | { 9 | 10 | /** 11 | * @brief RMCP+ and RAKP Message Status Codes 12 | */ 13 | enum class RAKP_ReturnCode : uint8_t 14 | { 15 | NO_ERROR, //!< No errors 16 | INSUFFICIENT_RESOURCE, //!< Insufficient resources to create a session 17 | INVALID_SESSION_ID, //!< Invalid Session ID 18 | INVALID_PAYLOAD_TYPE, //!< Invalid payload type 19 | INVALID_AUTH_ALGO, //!< Invalid authentication algorithm 20 | INVALID_INTEGRITY_ALGO, //!< Invalid integrity algorithm 21 | NO_MATCH_AUTH_PAYLOAD, //!< No matching authentication payload 22 | NO_MATCH_INTEGRITY_PAYLOAD, //!< No matching integrity payload 23 | INACTIVE_SESSIONID, //!< Inactive Session ID 24 | INACTIVE_ROLE, //!< Invalid role 25 | UNAUTH_ROLE_PRIV, //!< Unauthorized role or privilege requested 26 | INSUFFICIENT_RESOURCES_ROLE, //!< Insufficient resources to create a session 27 | INVALID_NAME_LENGTH, //!< Invalid name length 28 | UNAUTH_NAME, //!< Unauthorized name 29 | UNAUTH_GUID, //!< Unauthorized GUID 30 | INVALID_INTEGRITY_VALUE, //!< Invalid integrity check value 31 | INVALID_CONF_ALGO, //!< Invalid confidentiality algorithm 32 | NO_CIPHER_SUITE_MATCH, //!< No Cipher Suite match with security algos 33 | ILLEGAL_PARAMETER, //!< Illegal or unrecognized parameter 34 | }; 35 | 36 | /** 37 | * @brief Register Session Setup commands to the Command Table 38 | */ 39 | void sessionSetupCommands(); 40 | 41 | } // namespace command 42 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # OWNERS 2 | # ------ 3 | # 4 | # The OWNERS file maintains the list of individuals responsible for various 5 | # parts of this repository, including code review and approval. We use the 6 | # Gerrit 'owners' plugin, which consumes this file, along with some extra 7 | # keywords for our own purposes and tooling. 8 | # 9 | # For details on the configuration used by 'owners' see: 10 | # https://gerrit.googlesource.com/plugins/owners/+/refs/heads/master/owners/src/main/resources/Documentation/config.md 11 | # 12 | # An OWNERS file must be in the root of a repository but may also be present 13 | # in any subdirectory. The contents of the subdirectory OWNERS file are 14 | # combined with parent directories unless 'inherit: false' is set. 15 | # 16 | # The owners file is YAML and has [up to] 4 top-level keywords. 17 | # * owners: A list of individuals who have approval authority on the 18 | # repository. 19 | # 20 | # * reviewers: A list of individuals who have requested review notification 21 | # on the repository. 22 | # 23 | # * matchers: A list of specific file/path matchers for granular 'owners' and 24 | # 'reviewers'. See 'owners' plugin documentation. 25 | # 26 | # * openbmc: A list of openbmc-specific meta-data about owners and reviewers. 27 | # - name: preferred name of the individual. 28 | # - email: preferred email address of the individual. 29 | # - discord: Discord nickname of the individual. 30 | # 31 | # It is expected that these 4 sections will be listed in the order above and 32 | # data within them will be kept sorted. 33 | 34 | owners: 35 | - vernon.mauery@gmail.com 36 | 37 | reviewers: 38 | 39 | matchers: 40 | 41 | openbmc: 42 | - name: Vernon Mauery 43 | email: vernon.mauery@gmail.com 44 | discord: vmauery 45 | -------------------------------------------------------------------------------- /endian.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace endian 7 | { 8 | namespace details 9 | { 10 | template 11 | struct convert 12 | { 13 | static T to_ipmi(T) = delete; 14 | static T from_ipmi(T) = delete; 15 | static T to_network(T) = delete; 16 | static T from_network(T) = delete; 17 | }; 18 | 19 | template <> 20 | struct convert 21 | { 22 | static uint16_t to_ipmi(uint16_t i) 23 | { 24 | return htole16(i); 25 | }; 26 | static uint16_t from_ipmi(uint16_t i) 27 | { 28 | return le16toh(i); 29 | }; 30 | static uint16_t to_network(uint16_t i) 31 | { 32 | return htobe16(i); 33 | }; 34 | static uint16_t from_network(uint16_t i) 35 | { 36 | return be16toh(i); 37 | }; 38 | }; 39 | 40 | template <> 41 | struct convert 42 | { 43 | static uint32_t to_ipmi(uint32_t i) 44 | { 45 | return htole32(i); 46 | }; 47 | static uint32_t from_ipmi(uint32_t i) 48 | { 49 | return le32toh(i); 50 | }; 51 | static uint32_t to_network(uint32_t i) 52 | { 53 | return htobe32(i); 54 | }; 55 | static uint32_t from_network(uint32_t i) 56 | { 57 | return be32toh(i); 58 | }; 59 | }; 60 | } // namespace details 61 | 62 | template 63 | T to_ipmi(T i) 64 | { 65 | return details::convert::to_ipmi(i); 66 | } 67 | 68 | template 69 | T from_ipmi(T i) 70 | { 71 | return details::convert::from_ipmi(i); 72 | } 73 | 74 | template 75 | T to_network(T i) 76 | { 77 | return details::convert::to_network(i); 78 | } 79 | template 80 | T from_network(T i) 81 | { 82 | return details::convert::from_network(i); 83 | } 84 | } // namespace endian 85 | -------------------------------------------------------------------------------- /sol_module.cpp: -------------------------------------------------------------------------------- 1 | #include "command/payload_cmds.hpp" 2 | #include "command/sol_cmds.hpp" 3 | #include "command_table.hpp" 4 | #include "session.hpp" 5 | 6 | namespace sol 7 | { 8 | 9 | namespace command 10 | { 11 | 12 | void registerCommands() 13 | { 14 | static const ::command::CmdDetails commands[] = { 15 | // SOL Payload Handler 16 | {{(static_cast(message::PayloadType::SOL) << 16)}, 17 | &payloadHandler, 18 | session::Privilege::HIGHEST_MATCHING, 19 | false}, 20 | // Activate Payload Command 21 | {{(static_cast(message::PayloadType::IPMI) << 16) | 22 | static_cast(::command::NetFns::APP) | 0x48}, 23 | &activatePayload, 24 | session::Privilege::USER, 25 | false}, 26 | // Deactivate Payload Command 27 | {{(static_cast(message::PayloadType::IPMI) << 16) | 28 | static_cast(::command::NetFns::APP) | 0x49}, 29 | &deactivatePayload, 30 | session::Privilege::USER, 31 | false}, 32 | // Get Payload Activation Status 33 | {{(static_cast(message::PayloadType::IPMI) << 16) | 34 | static_cast(::command::NetFns::APP) | 0x4A}, 35 | &getPayloadStatus, 36 | session::Privilege::USER, 37 | false}, 38 | // Get Payload Instance Info Command 39 | {{(static_cast(message::PayloadType::IPMI) << 16) | 40 | static_cast(::command::NetFns::APP) | 0x4B}, 41 | &getPayloadInfo, 42 | session::Privilege::USER, 43 | false}, 44 | }; 45 | 46 | for (const auto& iter : commands) 47 | { 48 | ::command::Table::get().registerCommand( 49 | iter.command, 50 | std::make_unique<::command::NetIpmidEntry>( 51 | iter.command, iter.functor, iter.privilege, iter.sessionless)); 52 | } 53 | } 54 | 55 | } // namespace command 56 | 57 | } // namespace sol 58 | -------------------------------------------------------------------------------- /sol/console_buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace sol 10 | { 11 | 12 | using ConsoleBuffer = std::deque; 13 | 14 | /** @class ConsoleData 15 | * 16 | * The console data is the buffer that holds the data that comes from the host 17 | * console which is to be sent to the remote console. The buffer is needed due 18 | * to the latency with the IPMI remote client. The current support for the 19 | * buffer is to support one instance of the SOL payload. 20 | */ 21 | class ConsoleData 22 | { 23 | public: 24 | /** @brief Get the current size of the host console buffer. 25 | * 26 | * @return size of the host console buffer. 27 | */ 28 | auto size() const noexcept 29 | { 30 | return data.size(); 31 | } 32 | 33 | /** @brief Read host console data. 34 | * 35 | * This API would return the iterator to the read data from the 36 | * console data buffer. 37 | * 38 | * @return iterator to read data from the buffer 39 | */ 40 | auto read() const 41 | { 42 | return data.cbegin(); 43 | } 44 | 45 | /** @brief Write host console data. 46 | * 47 | * This API would append the input data to the host console buffer. 48 | * 49 | * @param[in] input - data to be written to the console buffer. 50 | */ 51 | void write(const std::vector& input) 52 | { 53 | data.insert(data.end(), input.begin(), input.end()); 54 | } 55 | 56 | /** @brief Erase console buffer. 57 | * 58 | * @param[in] size - the number of bytes to be erased from the console 59 | * buffer. 60 | * 61 | * @note If the console buffer has less bytes that that was requested, 62 | * then the available size is erased. 63 | */ 64 | void erase(size_t size) noexcept 65 | { 66 | data.erase(data.begin(), data.begin() + std::min(data.size(), size)); 67 | } 68 | 69 | private: 70 | /** @brief Storage for host console data. */ 71 | ConsoleBuffer data; 72 | }; 73 | 74 | } // namespace sol 75 | -------------------------------------------------------------------------------- /command/sol_cmds.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message_handler.hpp" 4 | 5 | #include 6 | 7 | namespace sol 8 | { 9 | 10 | namespace command 11 | { 12 | 13 | /** @brief SOL Payload Handler 14 | * 15 | * This command is used for activating and deactivating a payload type under a 16 | * given IPMI session. The UDP Port number for SOL is the same as the port that 17 | * was used to establish the IPMI session. 18 | * 19 | * @param[in] inPayload - Request data for the command. 20 | * @param[in] handler - Reference to the message handler. 21 | * 22 | * @return Response data for the command. 23 | */ 24 | std::vector payloadHandler(const std::vector& inPayload, 25 | std::shared_ptr& handler); 26 | 27 | constexpr uint8_t netfnTransport = 0x0C; 28 | constexpr uint8_t solActivatingCmd = 0x20; 29 | 30 | /** @struct ActivatingRequest 31 | * 32 | * IPMI payload for SOL Activating command. 33 | */ 34 | struct ActivatingRequest 35 | { 36 | #if BYTE_ORDER == LITTLE_ENDIAN 37 | uint8_t sessionState:4; //!< SOL session state. 38 | uint8_t reserved:4; //!< Reserved. 39 | #endif 40 | 41 | #if BYTE_ORDER == BIG_ENDIAN 42 | uint8_t reserved:4; //!< Reserved. 43 | uint8_t sessionState:4; //!< SOL session state. 44 | #endif 45 | 46 | uint8_t payloadInstance; //!< Payload instance. 47 | uint8_t majorVersion; //!< SOL format major version 48 | uint8_t minorVersion; //!< SOL format minor version 49 | } __attribute__((packed)); 50 | 51 | /** @brief SOL Activating Command. 52 | * 53 | * This command provides a mechanism for the BMC to notify a remote application 54 | * that a SOL payload is activating on another channel.The request message is a 55 | * message that is asynchronously generated by the BMC. The BMC will not wait 56 | * for a response from the remote console before dropping the serial connection 57 | * to proceed with SOL, therefore the remote console does not need to respond 58 | * to this command. 59 | * 60 | * @param[in] payloadInstance - SOL payload instance. 61 | * @param[in] sessionID - IPMI session ID. 62 | */ 63 | void activating(uint8_t payloadInstance, uint32_t sessionID); 64 | 65 | } // namespace command 66 | 67 | } // namespace sol 68 | -------------------------------------------------------------------------------- /command/sol_cmds.cpp: -------------------------------------------------------------------------------- 1 | #include "sol_cmds.hpp" 2 | 3 | #include "sessions_manager.hpp" 4 | #include "sol/sol_context.hpp" 5 | #include "sol/sol_manager.hpp" 6 | 7 | #include 8 | 9 | namespace sol 10 | { 11 | 12 | namespace command 13 | { 14 | 15 | using namespace phosphor::logging; 16 | 17 | std::vector payloadHandler(const std::vector& inPayload, 18 | std::shared_ptr& handler) 19 | { 20 | // Check inPayload size is at least Payload 21 | if (inPayload.size() < sizeof(Payload)) 22 | { 23 | return std::vector(); 24 | } 25 | 26 | auto request = reinterpret_cast(inPayload.data()); 27 | auto solDataSize = inPayload.size() - sizeof(Payload); 28 | 29 | std::vector charData(solDataSize); 30 | if (solDataSize > 0) 31 | { 32 | std::copy_n(inPayload.data() + sizeof(Payload), solDataSize, 33 | charData.begin()); 34 | } 35 | 36 | try 37 | { 38 | auto& context = sol::Manager::get().getContext(handler->sessionID); 39 | 40 | context.processInboundPayload( 41 | request->packetSeqNum, request->packetAckSeqNum, 42 | request->acceptedCharCount, request->inOperation.ack, 43 | request->inOperation.generateBreak, charData); 44 | } 45 | catch (const std::exception& e) 46 | { 47 | lg2::error("Failed to call the getContext method: {ERROR}", "ERROR", e); 48 | return std::vector(); 49 | } 50 | 51 | return std::vector(); 52 | } 53 | 54 | void activating(uint8_t payloadInstance, uint32_t sessionID) 55 | { 56 | std::vector outPayload(sizeof(ActivatingRequest)); 57 | 58 | auto request = reinterpret_cast(outPayload.data()); 59 | 60 | request->sessionState = 0; 61 | request->payloadInstance = payloadInstance; 62 | request->majorVersion = MAJOR_VERSION; 63 | request->minorVersion = MINOR_VERSION; 64 | 65 | auto session = session::Manager::get().getSession(sessionID); 66 | 67 | message::Handler msgHandler(session->channelPtr, sessionID); 68 | 69 | msgHandler.sendUnsolicitedIPMIPayload(netfnTransport, solActivatingCmd, 70 | outPayload); 71 | } 72 | } // namespace command 73 | 74 | } // namespace sol 75 | -------------------------------------------------------------------------------- /comm_module.cpp: -------------------------------------------------------------------------------- 1 | #include "comm_module.hpp" 2 | 3 | #include "command/channel_auth.hpp" 4 | #include "command/open_session.hpp" 5 | #include "command/rakp12.hpp" 6 | #include "command/rakp34.hpp" 7 | #include "command/session_cmds.hpp" 8 | #include "command_table.hpp" 9 | #include "session.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace command 16 | { 17 | 18 | void sessionSetupCommands() 19 | { 20 | static const command::CmdDetails commands[] = { 21 | // Open Session Request/Response 22 | {{(static_cast(message::PayloadType::OPEN_SESSION_REQUEST) 23 | << 16)}, 24 | &openSession, 25 | session::Privilege::HIGHEST_MATCHING, 26 | true}, 27 | // RAKP1 & RAKP2 Message 28 | {{(static_cast(message::PayloadType::RAKP1) << 16)}, 29 | &RAKP12, 30 | session::Privilege::HIGHEST_MATCHING, 31 | true}, 32 | // RAKP3 & RAKP4 Message 33 | {{(static_cast(message::PayloadType::RAKP3) << 16)}, 34 | &RAKP34, 35 | session::Privilege::HIGHEST_MATCHING, 36 | true}, 37 | // Get Channel Authentication Capabilities Command 38 | {{(static_cast(message::PayloadType::IPMI) << 16) | 39 | static_cast(command::NetFns::APP) | 0x38}, 40 | &GetChannelCapabilities, 41 | session::Privilege::HIGHEST_MATCHING, 42 | true}, 43 | // Get Channel Cipher Suites Command 44 | {{(static_cast(message::PayloadType::IPMI) << 16) | 45 | static_cast(::command::NetFns::APP) | 0x54}, 46 | &getChannelCipherSuites, 47 | session::Privilege::HIGHEST_MATCHING, 48 | true}, 49 | // Set Session Privilege Command 50 | {{(static_cast(message::PayloadType::IPMI) << 16) | 51 | static_cast(command::NetFns::APP) | 0x3B}, 52 | &setSessionPrivilegeLevel, 53 | session::Privilege::USER, 54 | false}, 55 | // Close Session Command 56 | {{(static_cast(message::PayloadType::IPMI) << 16) | 57 | static_cast(command::NetFns::APP) | 0x3C}, 58 | &closeSession, 59 | session::Privilege::CALLBACK, 60 | false}, 61 | }; 62 | 63 | for (auto& iter : commands) 64 | { 65 | command::Table::get().registerCommand( 66 | iter.command, 67 | std::make_unique( 68 | iter.command, iter.functor, iter.privilege, iter.sessionless)); 69 | } 70 | } 71 | 72 | } // namespace command 73 | -------------------------------------------------------------------------------- /auth_algo.cpp: -------------------------------------------------------------------------------- 1 | #include "auth_algo.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace cipher 12 | { 13 | 14 | namespace rakp_auth 15 | { 16 | 17 | std::vector AlgoSHA1::generateHMAC( 18 | const std::vector& input) const 19 | { 20 | std::vector output(SHA_DIGEST_LENGTH); 21 | unsigned int mdLen = 0; 22 | 23 | if (HMAC(EVP_sha1(), userKey.data(), userKey.size(), input.data(), 24 | input.size(), output.data(), &mdLen) == NULL) 25 | { 26 | lg2::error("Generate HMAC failed: {ERROR}", "ERROR", strerror(errno)); 27 | output.resize(0); 28 | } 29 | 30 | return output; 31 | } 32 | 33 | std::vector AlgoSHA1::generateICV( 34 | const std::vector& input) const 35 | { 36 | std::vector output(SHA_DIGEST_LENGTH); 37 | unsigned int mdLen = 0; 38 | 39 | if (HMAC(EVP_sha1(), sessionIntegrityKey.data(), SHA_DIGEST_LENGTH, 40 | input.data(), input.size(), output.data(), &mdLen) == NULL) 41 | { 42 | lg2::error("Generate Session Integrity Key failed: {ERROR}", "ERROR", 43 | strerror(errno)); 44 | output.resize(0); 45 | } 46 | output.resize(integrityCheckValueLength); 47 | 48 | return output; 49 | } 50 | 51 | std::vector AlgoSHA256::generateHMAC( 52 | const std::vector& input) const 53 | { 54 | std::vector output(SHA256_DIGEST_LENGTH); 55 | unsigned int mdLen = 0; 56 | 57 | if (HMAC(EVP_sha256(), userKey.data(), userKey.size(), input.data(), 58 | input.size(), output.data(), &mdLen) == NULL) 59 | { 60 | lg2::error("Generate HMAC_SHA256 failed: {ERROR}", "ERROR", 61 | strerror(errno)); 62 | output.resize(0); 63 | } 64 | 65 | return output; 66 | } 67 | 68 | std::vector AlgoSHA256::generateICV( 69 | const std::vector& input) const 70 | { 71 | std::vector output(SHA256_DIGEST_LENGTH); 72 | unsigned int mdLen = 0; 73 | 74 | if (HMAC(EVP_sha256(), sessionIntegrityKey.data(), 75 | sessionIntegrityKey.size(), input.data(), input.size(), 76 | output.data(), &mdLen) == NULL) 77 | { 78 | lg2::error( 79 | "Generate HMAC_SHA256_128 Integrity Check Value failed: {ERROR}", 80 | "ERROR", strerror(errno)); 81 | output.resize(0); 82 | } 83 | output.resize(integrityCheckValueLength); 84 | 85 | return output; 86 | } 87 | 88 | } // namespace rakp_auth 89 | 90 | } // namespace cipher 91 | -------------------------------------------------------------------------------- /command/rakp12.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "comm_module.hpp" 4 | #include "message_handler.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace command 10 | { 11 | 12 | constexpr size_t userNameMaxLen = 16; 13 | 14 | constexpr uint8_t userNameOnlyLookupMask = 0x10; 15 | constexpr uint8_t userNameOnlyLookup = 0x10; 16 | constexpr uint8_t userNamePrivLookup = 0x0; 17 | 18 | /** 19 | * @struct RAKP1request 20 | * 21 | * IPMI Payload for RAKP Message 1 22 | */ 23 | struct RAKP1request 24 | { 25 | uint8_t messageTag; 26 | uint8_t reserved1; 27 | uint16_t reserved2; 28 | uint32_t managedSystemSessionID; 29 | uint8_t remote_console_random_number[16]; 30 | uint8_t req_max_privilege_level; 31 | uint16_t reserved3; 32 | uint8_t user_name_len; 33 | char user_name[userNameMaxLen]; 34 | } __attribute__((packed)); 35 | 36 | /** 37 | * @struct RAKP2response 38 | * 39 | * IPMI Payload for RAKP Message 2 40 | */ 41 | struct RAKP2response 42 | { 43 | uint8_t messageTag; 44 | uint8_t rmcpStatusCode; 45 | uint16_t reserved; 46 | uint32_t remoteConsoleSessionID; 47 | uint8_t managed_system_random_number[16]; 48 | uint8_t managed_system_guid[16]; 49 | } __attribute__((packed)); 50 | 51 | /** 52 | * @brief RAKP Message 1, RAKP Message 2 53 | * 54 | * These messages are used to exchange random number and identification 55 | * information between the BMC and the remote console that are, in effect, 56 | * mutual challenges for a challenge/response. (Unlike IPMI v1.5, the v2.0/RMCP+ 57 | * challenge/response is symmetric. I.e. the remote console and BMC both issues 58 | * challenges,and both need to provide valid responses for the session to be 59 | * activated.) 60 | * 61 | * The remote console request (RAKP Message 1) passes a random number and 62 | * username/privilege information that the BMC will later use to ‘sign’ a 63 | * response message based on key information associated with the user and the 64 | * Authentication Algorithm negotiated in the Open Session Request/Response 65 | * exchange. The BMC responds with RAKP Message 2 and passes a random number and 66 | * GUID (globally unique ID) for the managed system that the remote console 67 | * uses according the Authentication Algorithm to sign a response back to the 68 | * BMC. 69 | * 70 | * @param[in] inPayload - Request Data for the command 71 | * @param[in] handler - Reference to the Message Handler 72 | * 73 | * @return Response data for the command 74 | */ 75 | std::vector RAKP12(const std::vector& inPayload, 76 | std::shared_ptr& handler); 77 | /** 78 | *@brief Log Redfish event for invalid login attempted on RMCPP interface 79 | * 80 | * @param[in] journalMsg - Show journal Debug Message in journal logs 81 | * @param[in] redfishMsg - Log Redfish Event Message 82 | * 83 | */ 84 | void logInvalidLoginRedfishEvent( 85 | const std::string& journalMsg, 86 | const std::optional& messageArgs = "RMCPP"); 87 | } // namespace command 88 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'phosphor-net-ipmid', 3 | 'cpp', 4 | version: '1.0.0', 5 | meson_version: '>=1.1.1', 6 | default_options: [ 7 | 'warning_level=3', 8 | 'werror=true', 9 | 'cpp_std=c++23', 10 | 'buildtype=debugoptimized', 11 | 'b_lto=true', 12 | ], 13 | ) 14 | 15 | conf_data = configuration_data() 16 | conf_data.set('RMCP_PING', get_option('rmcp_ping').allowed()) 17 | conf_data.set('PAM_AUTHENTICATE', get_option('pam_authenticate').allowed()) 18 | 19 | configure_file(output: 'config.h', configuration: conf_data) 20 | 21 | sdbusplus_dep = dependency('sdbusplus') 22 | phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces') 23 | phosphor_logging_dep = dependency('phosphor-logging') 24 | libsystemd_dep = dependency('libsystemd') 25 | libcrypto_dep = dependency('libcrypto') 26 | ipmid_dep = dependency('libipmid') 27 | userlayer_dep = dependency('libuserlayer') 28 | channellayer_dep = dependency('libchannellayer') 29 | 30 | # Project Arguments 31 | cpp = meson.get_compiler('cpp') 32 | if cpp.has_header('CLI/CLI.hpp') 33 | cli11_dep = declare_dependency() 34 | else 35 | cli11_dep = dependency('CLI11') 36 | endif 37 | 38 | add_project_arguments( 39 | cpp.get_supported_arguments( 40 | [ 41 | '-DBOOST_ERROR_CODE_HEADER_ONLY', 42 | '-DBOOST_SYSTEM_NO_DEPRECATED', 43 | '-DBOOST_COROUTINES_NO_DEPRECATION_WARNING', 44 | '-DBOOST_ASIO_DISABLE_THREADS', 45 | '-DBOOST_ALL_NO_LIB', 46 | ], 47 | ), 48 | language: 'cpp', 49 | ) 50 | 51 | deps = [ 52 | cli11_dep, 53 | ipmid_dep, 54 | userlayer_dep, 55 | channellayer_dep, 56 | libcrypto_dep, 57 | libsystemd_dep, 58 | phosphor_dbus_interfaces_dep, 59 | phosphor_logging_dep, 60 | sdbusplus_dep, 61 | ] 62 | 63 | sources = [ 64 | 'auth_algo.cpp', 65 | 'sessions_manager.cpp', 66 | 'message_parsers.cpp', 67 | 'message_handler.cpp', 68 | 'command_table.cpp', 69 | 'command/channel_auth.cpp', 70 | 'command/guid.cpp', 71 | 'command/open_session.cpp', 72 | 'command/rakp12.cpp', 73 | 'command/rakp34.cpp', 74 | 'command/session_cmds.cpp', 75 | 'comm_module.cpp', 76 | 'main.cpp', 77 | 'integrity_algo.cpp', 78 | 'crypt_algo.cpp', 79 | 'sd_event_loop.cpp', 80 | 'sol/sol_manager.cpp', 81 | 'sol/sol_context.cpp', 82 | 'command/sol_cmds.cpp', 83 | 'command/payload_cmds.cpp', 84 | 'sol_module.cpp', 85 | ] 86 | 87 | executable( 88 | 'netipmid', 89 | sources, 90 | implicit_include_directories: true, 91 | include_directories: ['command', 'sol'], 92 | dependencies: deps, 93 | install: true, 94 | install_dir: get_option('bindir'), 95 | ) 96 | 97 | systemd = dependency('systemd') 98 | systemd_system_unit_dir = systemd.get_variable( 99 | 'systemdsystemunitdir', 100 | pkgconfig_define: ['prefix', get_option('prefix')], 101 | ) 102 | 103 | configure_file( 104 | input: 'phosphor-ipmi-net@.service', 105 | output: 'phosphor-ipmi-net@.service', 106 | copy: true, 107 | install_dir: systemd_system_unit_dir, 108 | ) 109 | 110 | configure_file( 111 | input: 'phosphor-ipmi-net@.socket', 112 | output: 'phosphor-ipmi-net@.socket', 113 | copy: true, 114 | install_dir: systemd_system_unit_dir, 115 | ) 116 | 117 | build_tests = get_option('tests') 118 | if build_tests.allowed() 119 | subdir('test') 120 | endif 121 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "main.hpp" 2 | 3 | #include "comm_module.hpp" 4 | #include "command/guid.hpp" 5 | #include "command_table.hpp" 6 | #include "message.hpp" 7 | #include "message_handler.hpp" 8 | #include "sd_event_loop.hpp" 9 | #include "sessions_manager.hpp" 10 | #include "socket_channel.hpp" 11 | #include "sol_module.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | static auto io = std::make_shared(); 29 | std::shared_ptr getIo() 30 | { 31 | return io; 32 | } 33 | 34 | sd_bus* bus = nullptr; 35 | 36 | std::shared_ptr sdbusp; 37 | 38 | /* 39 | * @brief Required by apphandler IPMI Provider Library 40 | */ 41 | sd_bus* ipmid_get_sd_bus_connection() 42 | { 43 | return bus; 44 | } 45 | 46 | /* 47 | * @brief mechanism to get at sdbusplus object 48 | */ 49 | std::shared_ptr getSdBus() 50 | { 51 | return sdbusp; 52 | } 53 | 54 | static EInterfaceIndex currentInterfaceIndex = interfaceUnknown; 55 | static void setInterfaceIndex(const std::string& channel) 56 | { 57 | try 58 | { 59 | currentInterfaceIndex = 60 | static_cast(ipmi::getChannelByName(channel)); 61 | } 62 | catch (const std::exception& e) 63 | { 64 | lg2::error("Requested {NAME} is not a valid channel name: {ERROR}", 65 | "NAME", channel, "ERROR", e); 66 | } 67 | } 68 | EInterfaceIndex getInterfaceIndex(void) 69 | { 70 | return currentInterfaceIndex; 71 | } 72 | 73 | int main(int argc, char* argv[]) 74 | { 75 | CLI::App app("KCS RMCP+ bridge"); 76 | std::string channel; 77 | app.add_option("-c,--channel", channel, "channel name. e.g., eth0"); 78 | uint16_t port = ipmi::rmcpp::defaultPort; 79 | app.add_option("-p,--port", port, "port number"); 80 | bool verbose = false; 81 | app.add_option("-v,--verbose", verbose, "print more verbose output"); 82 | CLI11_PARSE(app, argc, argv); 83 | 84 | // Connect to system bus 85 | auto rc = sd_bus_default_system(&bus); 86 | if (rc < 0) 87 | { 88 | lg2::error("Failed to connect to system bus: {ERROR}", "ERROR", 89 | strerror(-rc)); 90 | return rc; 91 | } 92 | sdbusp = std::make_shared(*io, bus); 93 | 94 | auto& loop = eventloop::EventLoop::get(); 95 | loop.setupSignal(); 96 | 97 | ipmi::ipmiChannelInit(); 98 | if (channel.size()) 99 | { 100 | setInterfaceIndex(channel); 101 | } 102 | 103 | session::Manager::get().managerInit(channel); 104 | // Register callback to update cache for a GUID change and cache the GUID 105 | command::registerGUIDChangeCallback(); 106 | 107 | // Register callback to update cache for sol conf change 108 | sol::registerSolConfChangeCallbackHandler(channel); 109 | 110 | // Register the phosphor-net-ipmid session setup commands 111 | command::sessionSetupCommands(); 112 | 113 | // Register the phosphor-net-ipmid SOL commands 114 | sol::command::registerCommands(); 115 | 116 | if (loop.setupSocket(sdbusp, channel)) 117 | { 118 | return EXIT_FAILURE; 119 | } 120 | 121 | // Start Event Loop 122 | return loop.startEventLoop(); 123 | } 124 | -------------------------------------------------------------------------------- /sd_event_loop.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "main.hpp" 4 | #include "sol/sol_manager.hpp" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace ipmi 16 | { 17 | namespace rmcpp 18 | { 19 | constexpr uint16_t defaultPort = 623; 20 | } // namespace rmcpp 21 | } // namespace ipmi 22 | 23 | namespace eventloop 24 | { 25 | using DbusObjectPath = std::string; 26 | using DbusService = std::string; 27 | using DbusInterface = std::string; 28 | using ObjectTree = 29 | std::map>>; 30 | using Value = std::variant; 32 | // VLANs are a 12-bit value 33 | constexpr uint16_t VLAN_VALUE_MASK = 0x0fff; 34 | constexpr auto MAPPER_BUS_NAME = "xyz.openbmc_project.ObjectMapper"; 35 | constexpr auto MAPPER_OBJ = "/xyz/openbmc_project/object_mapper"; 36 | constexpr auto MAPPER_INTF = "xyz.openbmc_project.ObjectMapper"; 37 | constexpr auto PATH_ROOT = "/xyz/openbmc_project/network"; 38 | constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN"; 39 | constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface"; 40 | constexpr auto METHOD_GET = "Get"; 41 | constexpr auto PROP_INTF = "org.freedesktop.DBus.Properties"; 42 | 43 | class EventLoop 44 | { 45 | private: 46 | struct Private 47 | {}; 48 | 49 | public: 50 | EventLoop(std::shared_ptr& io, const Private&) : 51 | io(io) 52 | {} 53 | EventLoop() = delete; 54 | ~EventLoop() = default; 55 | EventLoop(const EventLoop&) = delete; 56 | EventLoop& operator=(const EventLoop&) = delete; 57 | EventLoop(EventLoop&&) = delete; 58 | EventLoop& operator=(EventLoop&&) = delete; 59 | 60 | /** 61 | * @brief Get a reference to the singleton EventLoop 62 | * 63 | * @return EventLoop reference 64 | */ 65 | static EventLoop& get() 66 | { 67 | static std::shared_ptr ptr = nullptr; 68 | if (!ptr) 69 | { 70 | std::shared_ptr io = getIo(); 71 | ptr = std::make_shared(io, Private()); 72 | } 73 | return *ptr; 74 | } 75 | 76 | /** @brief Initialise the event loop and add the handler for incoming 77 | * IPMI packets. 78 | * 79 | * @return EXIT_SUCCESS on success and EXIT_FAILURE on failure. 80 | */ 81 | int startEventLoop(); 82 | 83 | /** @brief Set up the socket (if systemd has not already) and 84 | * make sure that the bus name matches the specified channel 85 | */ 86 | int setupSocket(std::shared_ptr& bus, 87 | std::string iface, 88 | uint16_t reqPort = ipmi::rmcpp::defaultPort); 89 | 90 | /** @brief set up boost::asio signal handling */ 91 | void setupSignal(); 92 | 93 | private: 94 | /** @brief async handler for incoming udp packets */ 95 | void handleRmcpPacket(); 96 | 97 | /** @brief register the async handler for incoming udp packets */ 98 | void startRmcpReceive(); 99 | 100 | /** @brief get vlanid */ 101 | int getVLANID(const std::string channel); 102 | 103 | /** @brief boost::asio io context to run with 104 | */ 105 | std::shared_ptr io; 106 | 107 | /** @brief boost::asio udp socket 108 | */ 109 | std::shared_ptr udpSocket = nullptr; 110 | }; 111 | 112 | } // namespace eventloop 113 | -------------------------------------------------------------------------------- /command/session_cmds.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message_handler.hpp" 4 | 5 | #include 6 | 7 | namespace command 8 | { 9 | 10 | constexpr uint8_t IPMI_CC_INVALID_PRIV_LEVEL = 0x80; 11 | constexpr uint8_t IPMI_CC_EXCEEDS_USER_PRIV = 0x81; 12 | // bits 30 & 31 (MSB) hold the instanceID, hence shifting by 30 bits 13 | constexpr uint8_t myNetInstanceSessionIdShiftMask = 30; 14 | // bits 6 & 7 (MSB) hold the instanceID, hence shifting by 6 bits 15 | constexpr uint8_t myNetInstanceSessionHandleShiftMask = 6; 16 | 17 | /** 18 | * @struct SetSessionPrivLevelReq 19 | * 20 | * IPMI Request data for Set Session Privilege Level command 21 | */ 22 | struct SetSessionPrivLevelReq 23 | { 24 | #if BYTE_ORDER == LITTLE_ENDIAN 25 | uint8_t reqPrivLevel:4; 26 | uint8_t reserved:4; 27 | #endif 28 | 29 | #if BYTE_ORDER == BIG_ENDIAN 30 | uint8_t reserved:4; 31 | uint8_t reqPrivLevel:4; 32 | #endif 33 | 34 | } __attribute__((packed)); 35 | 36 | /** 37 | * @struct SetSessionPrivLevelResp 38 | * 39 | * IPMI Response data for Set Session Privilege Level command 40 | */ 41 | struct SetSessionPrivLevelResp 42 | { 43 | uint8_t completionCode; 44 | 45 | #if BYTE_ORDER == LITTLE_ENDIAN 46 | uint8_t newPrivLevel:4; 47 | uint8_t reserved:4; 48 | #endif 49 | 50 | #if BYTE_ORDER == BIG_ENDIAN 51 | uint8_t reserved:4; 52 | uint8_t newPrivLevel:4; 53 | #endif 54 | 55 | } __attribute__((packed)); 56 | 57 | /** 58 | * @brief Set Session Privilege Command 59 | * 60 | * This command is sent in authenticated format. When a session is activated, 61 | * the session is set to an initial privilege level. A session that is 62 | * activated at a maximum privilege level of Callback is set to an initial 63 | * privilege level of Callback and cannot be changed. All other sessions are 64 | * initially set to USER level, regardless of the maximum privilege level 65 | * requested in the RAKP Message 1. 66 | * 67 | * This command cannot be used to set a privilege level higher than the lowest 68 | * of the privilege level set for the user(via the Set User Access command) and 69 | * the privilege limit for the channel that was set via the Set Channel Access 70 | * command. 71 | * 72 | * @param[in] inPayload - Request Data for the command 73 | * @param[in] handler - Reference to the Message Handler 74 | * 75 | * @return Response data for the command 76 | */ 77 | std::vector setSessionPrivilegeLevel( 78 | const std::vector& inPayload, 79 | std::shared_ptr& handler); 80 | /** 81 | * @struct CloseSessionRequest 82 | * 83 | * IPMI Request data for Close Session command 84 | */ 85 | struct CloseSessionRequest 86 | { 87 | uint32_t sessionID; 88 | uint8_t sessionHandle; 89 | } __attribute__((packed)); 90 | 91 | /** 92 | * @struct CloseSessionResponse 93 | * 94 | * IPMI Response data for Close Session command 95 | */ 96 | struct CloseSessionResponse 97 | { 98 | uint8_t completionCode; 99 | } __attribute__((packed)); 100 | 101 | /** 102 | * @brief Close Session Command 103 | * 104 | * This command is used to immediately terminate a session in progress. It is 105 | * typically used to close the session that the user is communicating over, 106 | * though it can be used to other terminate sessions in progress (provided that 107 | * the user is operating at the appropriate privilege level, or the command is 108 | * executed over a local channel - e.g. the system interface). Closing 109 | * sessionless session ( session zero) is restricted in this command 110 | * 111 | * @param[in] inPayload - Request Data for the command 112 | * @param[in] handler - Reference to the Message Handler 113 | * 114 | * @return Response data for the command 115 | */ 116 | std::vector closeSession(const std::vector& inPayload, 117 | std::shared_ptr& handler); 118 | 119 | } // namespace command 120 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: Align 10 | AlignTrailingComments: 11 | Kind: Always 12 | OverEmptyLines: 1 13 | AllowAllParametersOfDeclarationOnNextLine: true 14 | AllowShortBlocksOnASingleLine: Empty 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Empty 17 | AllowShortIfStatementsOnASingleLine: Never 18 | AllowShortLambdasOnASingleLine: true 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakBeforeMultilineStrings: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BitFieldColonSpacing: None 24 | BraceWrapping: 25 | AfterCaseLabel: true 26 | AfterClass: true 27 | AfterControlStatement: true 28 | AfterEnum: true 29 | AfterExternBlock: true 30 | AfterFunction: true 31 | AfterNamespace: true 32 | AfterObjCDeclaration: true 33 | AfterStruct: true 34 | AfterUnion: true 35 | BeforeCatch: true 36 | BeforeElse: true 37 | BeforeLambdaBody: false 38 | BeforeWhile: false 39 | IndentBraces: false 40 | SplitEmptyFunction: false 41 | SplitEmptyRecord: false 42 | SplitEmptyNamespace: false 43 | BreakAfterAttributes: Never 44 | BreakAfterReturnType: Automatic 45 | BreakBeforeBinaryOperators: None 46 | BreakBeforeBraces: Custom 47 | BreakBeforeTernaryOperators: true 48 | BreakConstructorInitializers: AfterColon 49 | BreakInheritanceList: AfterColon 50 | BreakStringLiterals: false 51 | BreakTemplateDeclarations: Yes 52 | ColumnLimit: 80 53 | CommentPragmas: '^ IWYU pragma:' 54 | CompactNamespaces: false 55 | ConstructorInitializerIndentWidth: 4 56 | ContinuationIndentWidth: 4 57 | Cpp11BracedListStyle: true 58 | DerivePointerAlignment: false 59 | DisableFormat: false 60 | FixNamespaceComments: true 61 | ForEachMacros: 62 | - foreach 63 | - Q_FOREACH 64 | - BOOST_FOREACH 65 | IncludeBlocks: Regroup 66 | IncludeCategories: 67 | - Regex: '^[<"](gtest|gmock)' 68 | Priority: 7 69 | - Regex: '^"config.h"' 70 | Priority: -1 71 | - Regex: '^".*\.h"' 72 | Priority: 1 73 | - Regex: '^".*\.hpp"' 74 | Priority: 2 75 | - Regex: '^<.*\.h>' 76 | Priority: 3 77 | - Regex: '^<.*\.hpp>' 78 | Priority: 4 79 | - Regex: '^<.*' 80 | Priority: 5 81 | - Regex: '.*' 82 | Priority: 6 83 | IndentCaseLabels: true 84 | IndentExternBlock: NoIndent 85 | IndentRequiresClause: true 86 | IndentWidth: 4 87 | IndentWrappedFunctionNames: true 88 | InsertNewlineAtEOF: true 89 | KeepEmptyLinesAtTheStartOfBlocks: false 90 | LambdaBodyIndentation: Signature 91 | LineEnding: LF 92 | MacroBlockBegin: '' 93 | MacroBlockEnd: '' 94 | MaxEmptyLinesToKeep: 1 95 | NamespaceIndentation: None 96 | ObjCBlockIndentWidth: 2 97 | ObjCSpaceAfterProperty: false 98 | ObjCSpaceBeforeProtocolList: true 99 | PackConstructorInitializers: BinPack 100 | PenaltyBreakAssignment: 25 101 | PenaltyBreakBeforeFirstCallParameter: 50 102 | PenaltyBreakComment: 300 103 | PenaltyBreakFirstLessLess: 120 104 | PenaltyBreakString: 1000 105 | PenaltyBreakTemplateDeclaration: 10 106 | PenaltyExcessCharacter: 1000000 107 | PenaltyReturnTypeOnItsOwnLine: 150 108 | PenaltyIndentedWhitespace: 1 109 | PointerAlignment: Left 110 | QualifierAlignment: Left 111 | ReferenceAlignment: Left 112 | ReflowComments: true 113 | RequiresClausePosition: OwnLine 114 | RequiresExpressionIndentation: Keyword 115 | SortIncludes: CaseSensitive 116 | SortUsingDeclarations: true 117 | SpaceAfterCStyleCast: false 118 | SpaceAfterTemplateKeyword: true 119 | SpaceBeforeAssignmentOperators: true 120 | SpaceBeforeCpp11BracedList: false 121 | SpaceBeforeCtorInitializerColon: true 122 | SpaceBeforeInheritanceColon: true 123 | SpaceBeforeParens: ControlStatements 124 | SpaceBeforeRangeBasedForLoopColon: true 125 | SpaceInEmptyParentheses: false 126 | SpacesBeforeTrailingComments: 1 127 | SpacesInAngles: Never 128 | SpacesInContainerLiterals: true 129 | SpacesInCStyleCastParentheses: false 130 | SpacesInParentheses: false 131 | SpacesInSquareBrackets: false 132 | Standard: Latest 133 | TabWidth: 4 134 | UseTab: Never 135 | ... 136 | 137 | -------------------------------------------------------------------------------- /command/open_session.cpp: -------------------------------------------------------------------------------- 1 | #include "open_session.hpp" 2 | 3 | #include "comm_module.hpp" 4 | #include "endian.hpp" 5 | #include "sessions_manager.hpp" 6 | 7 | #include 8 | 9 | namespace command 10 | { 11 | 12 | std::vector openSession( 13 | const std::vector& inPayload, 14 | std::shared_ptr& /* handler */) 15 | { 16 | auto request = 17 | reinterpret_cast(inPayload.data()); 18 | if (inPayload.size() != sizeof(*request)) 19 | { 20 | std::vector errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID}; 21 | return errorPayload; 22 | } 23 | 24 | std::vector outPayload(sizeof(OpenSessionResponse)); 25 | auto response = reinterpret_cast(outPayload.data()); 26 | 27 | // Per the IPMI Spec, messageTag and remoteConsoleSessionID are always 28 | // returned 29 | response->messageTag = request->messageTag; 30 | response->remoteConsoleSessionID = request->remoteConsoleSessionID; 31 | 32 | // Check for valid Authentication Algorithms 33 | if (!cipher::rakp_auth::Interface::isAlgorithmSupported( 34 | static_cast(request->authAlgo))) 35 | { 36 | response->status_code = 37 | static_cast(RAKP_ReturnCode::INVALID_AUTH_ALGO); 38 | return outPayload; 39 | } 40 | 41 | // Check for valid Integrity Algorithms 42 | if (!cipher::integrity::Interface::isAlgorithmSupported( 43 | static_cast(request->intAlgo))) 44 | { 45 | response->status_code = 46 | static_cast(RAKP_ReturnCode::INVALID_INTEGRITY_ALGO); 47 | return outPayload; 48 | } 49 | 50 | session::Privilege priv; 51 | 52 | // 0h in the requested maximum privilege role field indicates highest level 53 | // matching proposed algorithms. The maximum privilege level the session 54 | // can take is set to Administrator level. In the RAKP12 command sequence 55 | // the session maximum privilege role is set again based on the user's 56 | // permitted privilege level. 57 | if (!request->maxPrivLevel) 58 | { 59 | priv = session::Privilege::ADMIN; 60 | } 61 | else 62 | { 63 | priv = static_cast(request->maxPrivLevel); 64 | } 65 | 66 | // Check for valid Confidentiality Algorithms 67 | if (!cipher::crypt::Interface::isAlgorithmSupported( 68 | static_cast(request->confAlgo))) 69 | { 70 | response->status_code = 71 | static_cast(RAKP_ReturnCode::INVALID_CONF_ALGO); 72 | return outPayload; 73 | } 74 | 75 | std::shared_ptr session; 76 | try 77 | { 78 | // Start an IPMI session 79 | session = session::Manager::get().startSession( 80 | endian::from_ipmi<>(request->remoteConsoleSessionID), priv, 81 | static_cast(request->authAlgo), 82 | static_cast(request->intAlgo), 83 | static_cast(request->confAlgo)); 84 | } 85 | catch (const std::exception& e) 86 | { 87 | response->status_code = 88 | static_cast(RAKP_ReturnCode::INSUFFICIENT_RESOURCE); 89 | lg2::error("openSession : Problem opening a session: {ERROR}", "ERROR", 90 | e); 91 | return outPayload; 92 | } 93 | 94 | response->status_code = static_cast(RAKP_ReturnCode::NO_ERROR); 95 | response->maxPrivLevel = static_cast(session->reqMaxPrivLevel); 96 | response->managedSystemSessionID = 97 | endian::to_ipmi<>(session->getBMCSessionID()); 98 | 99 | response->authPayload = request->authPayload; 100 | response->authPayloadLen = request->authPayloadLen; 101 | response->authAlgo = request->authAlgo; 102 | 103 | response->intPayload = request->intPayload; 104 | response->intPayloadLen = request->intPayloadLen; 105 | response->intAlgo = request->intAlgo; 106 | 107 | response->confPayload = request->confPayload; 108 | response->confPayloadLen = request->confPayloadLen; 109 | response->confAlgo = request->confAlgo; 110 | 111 | session->updateLastTransactionTime(); 112 | 113 | // Session state is Setup in progress 114 | session->state(static_cast(session::State::setupInProgress)); 115 | return outPayload; 116 | } 117 | 118 | } // namespace command 119 | -------------------------------------------------------------------------------- /command/open_session.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message_handler.hpp" 4 | 5 | #include 6 | 7 | namespace command 8 | { 9 | 10 | /** 11 | * @struct OpenSessionRequest 12 | * 13 | * IPMI Payload for RMCP+ Open Session Request 14 | */ 15 | struct OpenSessionRequest 16 | { 17 | uint8_t messageTag; // Message tag from request buffer 18 | 19 | #if BYTE_ORDER == LITTLE_ENDIAN 20 | uint8_t maxPrivLevel:4; // Requested maximum privilege level 21 | uint8_t reserved1:4; // Reserved for future definition 22 | #endif 23 | 24 | #if BYTE_ORDER == BIG_ENDIAN 25 | uint8_t reserved1:4; // Reserved for future definition 26 | uint8_t maxPrivLevel:4; // Requested maximum privilege level 27 | 28 | #endif 29 | 30 | uint16_t reserved2; 31 | uint32_t remoteConsoleSessionID; 32 | 33 | uint8_t authPayload; 34 | uint16_t reserved3; 35 | uint8_t authPayloadLen; 36 | 37 | #if BYTE_ORDER == LITTLE_ENDIAN 38 | uint8_t authAlgo:6; 39 | uint8_t reserved4:2; 40 | #endif 41 | 42 | #if BYTE_ORDER == BIG_ENDIAN 43 | uint8_t reserved4:2; 44 | uint8_t authAlgo:6; 45 | #endif 46 | 47 | uint8_t reserved5; 48 | uint16_t reserved6; 49 | 50 | uint8_t intPayload; 51 | uint16_t reserved7; 52 | uint8_t intPayloadLen; 53 | 54 | #if BYTE_ORDER == LITTLE_ENDIAN 55 | uint8_t intAlgo:6; 56 | uint8_t reserved8:2; 57 | #endif 58 | 59 | #if BYTE_ORDER == BIG_ENDIAN 60 | uint8_t reserved8:2; 61 | uint8_t intAlgo:6; 62 | #endif 63 | 64 | uint8_t reserved9; 65 | uint16_t reserved10; 66 | 67 | uint8_t confPayload; 68 | uint16_t reserved11; 69 | uint8_t confPayloadLen; 70 | 71 | #if BYTE_ORDER == LITTLE_ENDIAN 72 | uint8_t confAlgo:6; 73 | uint8_t reserved12:2; 74 | #endif 75 | 76 | #if BYTE_ORDER == BIG_ENDIAN 77 | uint8_t reserved12:2; 78 | uint8_t confAlgo:6; 79 | #endif 80 | 81 | uint8_t reserved13; 82 | uint16_t reserved14; 83 | } __attribute__((packed)); 84 | 85 | /** 86 | * @struct OpenSessionResponse 87 | * 88 | * IPMI Payload for RMCP+ Open Session Response 89 | */ 90 | struct OpenSessionResponse 91 | { 92 | uint8_t messageTag; 93 | uint8_t status_code; 94 | 95 | #if BYTE_ORDER == LITTLE_ENDIAN 96 | uint8_t maxPrivLevel:4; 97 | uint8_t reserved1:4; 98 | #endif 99 | 100 | #if BYTE_ORDER == BIG_ENDIAN 101 | uint8_t reserved1:4; 102 | uint8_t maxPrivLevel:4; 103 | #endif 104 | 105 | uint8_t reserved2; 106 | uint32_t remoteConsoleSessionID; 107 | uint32_t managedSystemSessionID; 108 | 109 | uint8_t authPayload; 110 | uint16_t reserved3; 111 | uint8_t authPayloadLen; 112 | 113 | #if BYTE_ORDER == LITTLE_ENDIAN 114 | uint8_t authAlgo:6; 115 | uint8_t reserved4:2; 116 | #endif 117 | 118 | #if BYTE_ORDER == BIG_ENDIAN 119 | uint8_t reserved4:2; 120 | uint8_t authAlgo:6; 121 | #endif 122 | 123 | uint8_t reserved5; 124 | uint16_t reserved6; 125 | 126 | uint8_t intPayload; 127 | uint16_t reserved7; 128 | uint8_t intPayloadLen; 129 | 130 | #if BYTE_ORDER == LITTLE_ENDIAN 131 | uint8_t intAlgo:6; 132 | uint8_t reserved8:2; 133 | #endif 134 | 135 | #if BYTE_ORDER == BIG_ENDIAN 136 | uint8_t reserved8:2; 137 | uint8_t intAlgo:6; 138 | 139 | #endif 140 | 141 | uint8_t reserved9; 142 | uint16_t reserved10; 143 | 144 | uint8_t confPayload; 145 | uint16_t reserved11; 146 | uint8_t confPayloadLen; 147 | 148 | #if BYTE_ORDER == LITTLE_ENDIAN 149 | uint8_t confAlgo:6; 150 | uint8_t reserved12:2; 151 | #endif 152 | 153 | #if BYTE_ORDER == BIG_ENDIAN 154 | uint8_t reserved12:2; 155 | uint8_t confAlgo:6; 156 | #endif 157 | 158 | uint8_t reserved13; 159 | uint16_t reserved14; 160 | } __attribute__((packed)); 161 | 162 | /** 163 | * @brief RMCP+ Open Session Request, RMCP+ Open Session Response 164 | * 165 | * The RMCP+ Open Session request and response messages are used to enable a 166 | * remote console to discover what Cipher Suite(s) can be used for establishing 167 | * a session at a requested maximum privilege level. These messages are also 168 | * used for transferring the sessions IDs that the remote console and BMC wish 169 | * to for the session once it’s been activated, and to track each party during 170 | * the exchange of messages used for establishing the session. 171 | * 172 | * @param[in] inPayload - Request Data for the command 173 | * @param[in] handler - Reference to the Message Handler 174 | * 175 | * @return Response data for the command 176 | */ 177 | std::vector openSession(const std::vector& inPayload, 178 | std::shared_ptr& handler); 179 | 180 | } // namespace command 181 | -------------------------------------------------------------------------------- /command/channel_auth.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message_handler.hpp" 4 | 5 | #include 6 | 7 | namespace command 8 | { 9 | 10 | /** 11 | * @struct GetChannelCapabilitiesReq 12 | * 13 | * IPMI Request data for Get Channel Authentication Capabilities command 14 | */ 15 | struct GetChannelCapabilitiesReq 16 | { 17 | uint8_t channelNumber; 18 | uint8_t reqMaxPrivLevel; 19 | } __attribute__((packed)); 20 | 21 | /** 22 | * @struct GetChannelCapabilitiesResp 23 | * 24 | * IPMI Response data for Get Channel Authentication Capabilities command 25 | */ 26 | struct GetChannelCapabilitiesResp 27 | { 28 | uint8_t completionCode; // Completion Code 29 | 30 | uint8_t channelNumber; // Channel number that the request was 31 | // received on 32 | 33 | #if BYTE_ORDER == LITTLE_ENDIAN 34 | uint8_t none:1; 35 | uint8_t md2:1; 36 | uint8_t md5:1; 37 | uint8_t reserved2:1; 38 | uint8_t straightKey:1; // Straight password/key support 39 | // Support OEM identified by the IANA OEM ID in RMCP+ ping response 40 | uint8_t oem:1; 41 | uint8_t reserved1:1; 42 | uint8_t ipmiVersion:1; // 0b = IPMIV1.5 support only, 1B = IPMI V2.0 43 | // support 44 | #endif 45 | 46 | #if BYTE_ORDER == BIG_ENDIAN 47 | uint8_t ipmiVersion:1; // 0b = IPMIV1.5 support only, 1B = IPMI V2.0 48 | // support 49 | uint8_t reserved1:1; 50 | // Support OEM identified by the IANA OEM ID in RMCP+ ping response 51 | uint8_t oem:1; 52 | uint8_t straightKey:1; // Straight password/key support 53 | uint8_t reserved2:1; 54 | uint8_t md5:1; 55 | uint8_t md2:1; 56 | uint8_t none:1; 57 | #endif 58 | 59 | #if BYTE_ORDER == LITTLE_ENDIAN 60 | // Anonymous login status for anonymous login enabled/disabled 61 | uint8_t anonymousLogin:1; 62 | // Anonymous login status for null usernames enabled/disabled 63 | uint8_t nullUsers:1; 64 | // Anonymous login status for non-null usernames enabled/disabled 65 | uint8_t nonNullUsers:1; 66 | uint8_t userAuth:1; // User level authentication status 67 | uint8_t perMessageAuth:1; // Per-message authentication support 68 | // Two key login status . only for IPMI V2.0 RMCP+ RAKP 69 | uint8_t KGStatus:1; 70 | uint8_t reserved3:2; 71 | #endif 72 | 73 | #if BYTE_ORDER == BIG_ENDIAN 74 | uint8_t reserved3:2; 75 | // Two key login status . only for IPMI V2.0 RMCP+ RAKP 76 | uint8_t KGStatus:1; 77 | uint8_t perMessageAuth:1; // Per-message authentication support 78 | uint8_t userAuth:1; // User level authentication status 79 | // Anonymous login status for non-null usernames enabled/disabled 80 | uint8_t nonNullUsers:1; 81 | // Anonymous login status for null usernames enabled/disabled 82 | uint8_t nullUsers:1; 83 | // Anonymous login status for anonymous login enabled/disabled 84 | uint8_t anonymousLogin:1; 85 | #endif 86 | 87 | #if BYTE_ORDER == LITTLE_ENDIAN 88 | // Extended capabilities will be present only if IPMI version is V2.0 89 | uint8_t extCapabilities:2; // Channel support for IPMI V2.0 connections 90 | uint8_t reserved4:6; 91 | #endif 92 | 93 | #if BYTE_ORDER == BIG_ENDIAN 94 | // Extended capabilities will be present only if IPMI version is V2.0 95 | uint8_t reserved4:6; 96 | uint8_t extCapabilities:2; // Channel support for IPMI V2.0 connections 97 | #endif 98 | 99 | // Below 4 bytes will all the 0's if no OEM authentication type available. 100 | uint8_t oemID[3]; // IANA enterprise number for OEM/organization 101 | uint8_t oemAuxillary; // Addition OEM specific information.. 102 | } __attribute__((packed)); 103 | 104 | /** 105 | * @brief Get Channel Authentication Capabilities 106 | * 107 | * This message exchange provides a way for a remote console to discover what 108 | * IPMI version is supported i.e. whether or not the BMC supports the IPMI 109 | * v2.0 / RMCP+ packet format. It also provides information that the remote 110 | * console can use to determine whether anonymous, “one-key”, or “two-key” 111 | * logins are used.This information can guide a remote console in how it 112 | * presents queries to users for username and password information. This is a 113 | * ‘session-less’ command that the BMC accepts in both IPMI v1.5 and v2.0/RMCP+ 114 | * packet formats. 115 | * 116 | * @param[in] inPayload - Request Data for the command 117 | * @param[in] handler - Reference to the Message Handler 118 | * 119 | * @return Response data for the command 120 | */ 121 | std::vector GetChannelCapabilities( 122 | const std::vector& inPayload, 123 | std::shared_ptr& handler); 124 | 125 | /** 126 | * @brief Get Channel Cipher Suites 127 | * 128 | * This command is used to look up what authentication, integrity, and 129 | * confidentiality algorithms are supported. The algorithms are used in 130 | * combination as ‘Cipher Suites’. This command only applies to implementations 131 | * that support IPMI v2.0/RMCP+ sessions. This command can be executed prior to 132 | * establishing a session with the BMC. 133 | * 134 | * @param[in] inPayload - Request Data for the command 135 | * @param[in] handler - Reference to the Message Handler 136 | * 137 | * @return Response data for the command 138 | */ 139 | std::vector getChannelCipherSuites( 140 | const std::vector& inPayload, 141 | std::shared_ptr& handler); 142 | 143 | } // namespace command 144 | -------------------------------------------------------------------------------- /integrity_algo.cpp: -------------------------------------------------------------------------------- 1 | #include "integrity_algo.hpp" 2 | 3 | #include "message_parsers.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cipher 10 | { 11 | 12 | namespace integrity 13 | { 14 | 15 | AlgoSHA1::AlgoSHA1(const std::vector& sik) : 16 | Interface(SHA1_96_AUTHCODE_LENGTH) 17 | { 18 | k1 = generateKn(sik, rmcp::const_1); 19 | } 20 | 21 | std::vector AlgoSHA1::generateHMAC(const uint8_t* input, 22 | const size_t len) const 23 | { 24 | std::vector output(SHA_DIGEST_LENGTH); 25 | unsigned int mdLen = 0; 26 | 27 | if (HMAC(EVP_sha1(), k1.data(), k1.size(), input, len, output.data(), 28 | &mdLen) == NULL) 29 | { 30 | throw std::runtime_error("Generating integrity data failed"); 31 | } 32 | 33 | // HMAC generates Message Digest to the size of SHA_DIGEST_LENGTH, the 34 | // AuthCode field length is based on the integrity algorithm. So we are 35 | // interested only in the AuthCode field length of the generated Message 36 | // digest. 37 | output.resize(authCodeLength); 38 | 39 | return output; 40 | } 41 | 42 | bool AlgoSHA1::verifyIntegrityData( 43 | const std::vector& packet, const size_t length, 44 | std::vector::const_iterator integrityDataBegin, 45 | std::vector::const_iterator integrityDataEnd) const 46 | { 47 | auto output = generateHMAC( 48 | packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, length); 49 | 50 | // Verify if the generated integrity data for the packet and the received 51 | // integrity data matches. 52 | return (std::equal(output.begin(), output.end(), integrityDataBegin, 53 | integrityDataEnd)); 54 | } 55 | 56 | std::vector AlgoSHA1::generateIntegrityData( 57 | const std::vector& packet) const 58 | { 59 | return generateHMAC( 60 | packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, 61 | packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE); 62 | } 63 | 64 | std::vector AlgoSHA1::generateKn(const std::vector& sik, 65 | const rmcp::Const_n& const_n) const 66 | { 67 | unsigned int mdLen = 0; 68 | std::vector Kn(sik.size()); 69 | 70 | // Generated Kn for the integrity algorithm with the additional key keyed 71 | // with SIK. 72 | if (HMAC(EVP_sha1(), sik.data(), sik.size(), const_n.data(), const_n.size(), 73 | Kn.data(), &mdLen) == NULL) 74 | { 75 | throw std::runtime_error("Generating KeyN for integrity " 76 | "algorithm failed"); 77 | } 78 | return Kn; 79 | } 80 | 81 | AlgoSHA256::AlgoSHA256(const std::vector& sik) : 82 | Interface(SHA256_128_AUTHCODE_LENGTH) 83 | { 84 | k1 = generateKn(sik, rmcp::const_1); 85 | } 86 | 87 | std::vector AlgoSHA256::generateHMAC(const uint8_t* input, 88 | const size_t len) const 89 | { 90 | std::vector output(SHA256_DIGEST_LENGTH); 91 | unsigned int mdLen = 0; 92 | 93 | if (HMAC(EVP_sha256(), k1.data(), k1.size(), input, len, output.data(), 94 | &mdLen) == NULL) 95 | { 96 | throw std::runtime_error("Generating HMAC_SHA256_128 failed"); 97 | } 98 | 99 | // HMAC generates Message Digest to the size of SHA256_DIGEST_LENGTH, the 100 | // AuthCode field length is based on the integrity algorithm. So we are 101 | // interested only in the AuthCode field length of the generated Message 102 | // digest. 103 | output.resize(authCodeLength); 104 | 105 | return output; 106 | } 107 | 108 | bool AlgoSHA256::verifyIntegrityData( 109 | const std::vector& packet, const size_t length, 110 | std::vector::const_iterator integrityDataBegin, 111 | std::vector::const_iterator integrityDataEnd) const 112 | { 113 | auto output = generateHMAC( 114 | packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, length); 115 | 116 | // Verify if the generated integrity data for the packet and the received 117 | // integrity data matches. 118 | return (std::equal(output.begin(), output.end(), integrityDataBegin, 119 | integrityDataEnd)); 120 | } 121 | 122 | std::vector AlgoSHA256::generateIntegrityData( 123 | const std::vector& packet) const 124 | { 125 | return generateHMAC( 126 | packet.data() + message::parser::RMCP_SESSION_HEADER_SIZE, 127 | packet.size() - message::parser::RMCP_SESSION_HEADER_SIZE); 128 | } 129 | 130 | std::vector AlgoSHA256::generateKn(const std::vector& sik, 131 | const rmcp::Const_n& const_n) const 132 | { 133 | unsigned int mdLen = 0; 134 | std::vector Kn(sik.size()); 135 | 136 | // Generated Kn for the integrity algorithm with the additional key keyed 137 | // with SIK. 138 | if (HMAC(EVP_sha256(), sik.data(), sik.size(), const_n.data(), 139 | const_n.size(), Kn.data(), &mdLen) == NULL) 140 | { 141 | throw std::runtime_error("Generating KeyN for integrity " 142 | "algorithm HMAC_SHA256 failed"); 143 | } 144 | return Kn; 145 | } 146 | 147 | } // namespace integrity 148 | 149 | } // namespace cipher 150 | -------------------------------------------------------------------------------- /message_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message.hpp" 4 | #include "message_parsers.hpp" 5 | #include "session.hpp" 6 | #include "sessions_manager.hpp" 7 | #include "sol/console_buffer.hpp" 8 | 9 | #include 10 | 11 | namespace message 12 | { 13 | 14 | class Handler : public std::enable_shared_from_this 15 | { 16 | public: 17 | /** 18 | * @brief Create a Handler intended for a full transaction 19 | * that may or may not use asynchronous responses 20 | */ 21 | Handler(std::shared_ptr channel, 22 | std::shared_ptr io, 23 | uint32_t sessionID = message::Message::MESSAGE_INVALID_SESSION_ID) : 24 | sessionID(sessionID), channel(channel), io(io) 25 | { 26 | if (sessionID != message::Message::MESSAGE_INVALID_SESSION_ID) 27 | { 28 | session = session::Manager::get().getSession(sessionID); 29 | } 30 | } 31 | 32 | /** 33 | * @brief Create a Handler intended for a send only (SOL) 34 | */ 35 | Handler(std::shared_ptr channel, 36 | uint32_t sessionID = message::Message::MESSAGE_INVALID_SESSION_ID) : 37 | sessionID(sessionID), channel(channel), io(nullptr) 38 | { 39 | if (sessionID != message::Message::MESSAGE_INVALID_SESSION_ID) 40 | { 41 | session = session::Manager::get().getSession(sessionID); 42 | } 43 | } 44 | 45 | ~Handler(); 46 | Handler() = delete; 47 | Handler(const Handler&) = delete; 48 | Handler& operator=(const Handler&) = delete; 49 | Handler(Handler&&) = delete; 50 | Handler& operator=(Handler&&) = delete; 51 | 52 | /** 53 | * @brief Process the incoming IPMI message 54 | * 55 | * The incoming payload is read from the channel. If a message is read, it 56 | * is passed onto executeCommand, which may or may not execute the command 57 | * asynchrounously. If the command is executed asynchrounously, a shared_ptr 58 | * of self via shared_from_this will keep this object alive until the 59 | * response is ready. Then on the destructor, the response will be sent. 60 | */ 61 | void processIncoming(); 62 | 63 | /** @brief Set socket channel in session object */ 64 | void setChannelInSession() const; 65 | 66 | /** @brief Send the SOL payload 67 | * 68 | * The SOL payload is flattened and sent out on the socket 69 | * 70 | * @param[in] input - SOL Payload 71 | */ 72 | void sendSOLPayload(const std::vector& input); 73 | 74 | /** @brief Send the unsolicited IPMI payload to the remote console. 75 | * 76 | * This is used by commands like SOL activating, in which case the BMC 77 | * has to notify the remote console that a SOL payload is activating 78 | * on another channel. 79 | * 80 | * @param[in] netfn - Net function. 81 | * @param[in] cmd - Command. 82 | * @param[in] input - Command request data. 83 | */ 84 | void sendUnsolicitedIPMIPayload(uint8_t netfn, uint8_t cmd, 85 | const std::vector& input); 86 | 87 | // BMC Session ID for the Channel 88 | session::SessionID sessionID; 89 | 90 | /** @brief response to send back */ 91 | std::optional> outPayload; 92 | 93 | private: 94 | /** 95 | * @brief Receive the IPMI packet 96 | * 97 | * Read the data on the socket, get the parser based on the Session 98 | * header type and flatten the payload and generate the IPMI message 99 | */ 100 | bool receive(); 101 | 102 | /** 103 | * @brief Get Session data from the IPMI packet 104 | * 105 | */ 106 | void updSessionData(std::shared_ptr& inMessage); 107 | 108 | /** 109 | * @brief Process the incoming IPMI message 110 | * 111 | * The incoming message payload is handled and the command handler for 112 | * the Network function and Command is executed and the response message 113 | * is returned 114 | */ 115 | void executeCommand(); 116 | 117 | /** @brief Send the outgoing message 118 | * 119 | * The payload in the outgoing message is flattened and sent out on the 120 | * socket 121 | * 122 | * @param[in] outMessage - Outgoing Message 123 | */ 124 | void send(std::shared_ptr outMessage); 125 | 126 | #ifdef RMCP_PING 127 | /** @brief Send the outgoing ASF message 128 | * 129 | * The outgoing ASF message contains only ASF message header 130 | * which is flattened and sent out on the socket 131 | */ 132 | void sendASF(); 133 | #endif // RMCP_PING 134 | 135 | /** @brief Write the packet to the socket 136 | * 137 | * @param[in] packet - Outgoing packet 138 | */ 139 | void writeData(const std::vector& packet); 140 | 141 | /** @brief Socket channel for communicating with the remote client.*/ 142 | std::shared_ptr channel; 143 | 144 | /** @brief asio io context to run asynchrounously */ 145 | std::shared_ptr io; 146 | 147 | parser::SessionHeader sessionHeader = parser::SessionHeader::IPMI20; 148 | 149 | std::shared_ptr inMessage{}; 150 | 151 | /** @brief The IPMI session of the handler */ 152 | std::shared_ptr session{}; 153 | }; 154 | 155 | } // namespace message 156 | -------------------------------------------------------------------------------- /command/guid.cpp: -------------------------------------------------------------------------------- 1 | #include "guid.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | using namespace phosphor::logging; 14 | using namespace sdbusplus::xyz::openbmc_project::Common::Error; 15 | 16 | static std::optional guid; 17 | 18 | namespace command 19 | { 20 | 21 | std::unique_ptr matchPtr(nullptr); 22 | 23 | static constexpr auto propInterface = "xyz.openbmc_project.Common.UUID"; 24 | static constexpr auto uuidProperty = "UUID"; 25 | static constexpr auto subtreePath = "/xyz/openbmc_project/inventory"; 26 | 27 | static void rfcToGuid(std::string rfc4122, Guid& uuid) 28 | { 29 | using Argument = xyz::openbmc_project::Common::InvalidArgument; 30 | // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223 31 | // Per IPMI Spec 2.0 need to convert to 16 hex bytes and reverse the byte 32 | // order 33 | // Ex: 0x2332fc2c40e66298e511f2782395a361 34 | constexpr size_t uuidHexLength = (2 * BMC_GUID_LEN); 35 | constexpr size_t uuidRfc4122Length = (uuidHexLength + 4); 36 | 37 | if (rfc4122.size() == uuidRfc4122Length) 38 | { 39 | rfc4122.erase(std::remove(rfc4122.begin(), rfc4122.end(), '-'), 40 | rfc4122.end()); 41 | } 42 | if (rfc4122.size() != uuidHexLength) 43 | { 44 | elog(Argument::ARGUMENT_NAME("rfc4122"), 45 | Argument::ARGUMENT_VALUE(rfc4122.c_str())); 46 | } 47 | for (size_t ind = 0; ind < uuidHexLength; ind += 2) 48 | { 49 | long b; 50 | try 51 | { 52 | b = std::stoul(rfc4122.substr(ind, 2), nullptr, 16); 53 | } 54 | catch (const std::exception& e) 55 | { 56 | elog(Argument::ARGUMENT_NAME("rfc4122"), 57 | Argument::ARGUMENT_VALUE(rfc4122.c_str())); 58 | } 59 | 60 | uuid[BMC_GUID_LEN - (ind / 2) - 1] = static_cast(b); 61 | } 62 | return; 63 | } 64 | 65 | // Canned System GUID for when the Chassis DBUS object is not populated 66 | static constexpr Guid fakeGuid = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 67 | 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 68 | 0x0D, 0x0E, 0x0F, 0x10}; 69 | const Guid& getSystemGUID() 70 | { 71 | if (guid.has_value()) 72 | { 73 | return guid.value(); 74 | } 75 | 76 | sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 77 | 78 | ipmi::Value propValue; 79 | try 80 | { 81 | const auto& [objPath, service] = 82 | ipmi::getDbusObject(bus, propInterface, subtreePath); 83 | // Read UUID property value from bmcObject 84 | // UUID is in RFC4122 format Ex: 61a39523-78f2-11e5-9862-e6402cfc3223 85 | propValue = ipmi::getDbusProperty(bus, service, objPath, propInterface, 86 | uuidProperty); 87 | } 88 | catch (const sdbusplus::exception_t& e) 89 | { 90 | lg2::error("Failed in reading BMC UUID property: {ERROR}", "ERROR", e); 91 | return fakeGuid; 92 | } 93 | 94 | std::string rfc4122Uuid = std::get(propValue); 95 | try 96 | { 97 | // convert to IPMI format 98 | Guid tmpGuid{}; 99 | rfcToGuid(rfc4122Uuid, tmpGuid); 100 | guid = tmpGuid; 101 | } 102 | catch (const InvalidArgument& e) 103 | { 104 | lg2::error("Failed in parsing BMC UUID property: {VALUE}", "VALUE", 105 | rfc4122Uuid.c_str()); 106 | return fakeGuid; 107 | } 108 | return guid.value(); 109 | } 110 | 111 | void registerGUIDChangeCallback() 112 | { 113 | if (matchPtr == nullptr) 114 | { 115 | using namespace sdbusplus::bus::match::rules; 116 | sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 117 | 118 | try 119 | { 120 | matchPtr = std::make_unique( 121 | bus, propertiesChangedNamespace(subtreePath, propInterface), 122 | [](sdbusplus::message_t& m) { 123 | try 124 | { 125 | std::string iface{}; 126 | std::map pdict{}; 127 | m.read(iface, pdict); 128 | if (iface != propInterface) 129 | { 130 | return; 131 | } 132 | auto guidStr = std::get(pdict.at("UUID")); 133 | Guid tmpGuid{}; 134 | rfcToGuid(guidStr, tmpGuid); 135 | guid = tmpGuid; 136 | } 137 | catch (const std::exception& e) 138 | { 139 | // signal contained invalid guid; ignore it 140 | lg2::error( 141 | "Failed to parse propertiesChanged signal: {ERROR}", 142 | "ERROR", e); 143 | } 144 | }); 145 | } 146 | catch (const std::exception& e) 147 | { 148 | lg2::error("Failed to create dbus match: {ERROR}", "ERROR", e); 149 | } 150 | } 151 | } 152 | 153 | } // namespace command 154 | -------------------------------------------------------------------------------- /sessions_manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "main.hpp" 4 | #include "session.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace session 17 | { 18 | 19 | enum class RetrieveOption 20 | { 21 | BMC_SESSION_ID, 22 | RC_SESSION_ID, 23 | }; 24 | 25 | static constexpr size_t maxSessionHandles = multiIntfaceSessionHandleMask; 26 | 27 | /** 28 | * @class Manager 29 | * 30 | * Manager class acts a manager for the IPMI sessions and provides interfaces 31 | * to start a session, stop a session and get reference to the session objects. 32 | * 33 | */ 34 | 35 | class Manager 36 | { 37 | private: 38 | struct Private 39 | {}; 40 | 41 | public: 42 | // BMC Session ID is the key for the map 43 | using SessionMap = std::map>; 44 | 45 | Manager() = delete; 46 | Manager(std::shared_ptr& io, const Private&) : 47 | io(io), timer(*io) {}; 48 | ~Manager() = default; 49 | Manager(const Manager&) = delete; 50 | Manager& operator=(const Manager&) = delete; 51 | Manager(Manager&&) = default; 52 | Manager& operator=(Manager&&) = default; 53 | 54 | /** 55 | * @brief Get a reference to the singleton Manager 56 | * 57 | * @return Manager reference 58 | */ 59 | static Manager& get() 60 | { 61 | static std::shared_ptr ptr = nullptr; 62 | if (!ptr) 63 | { 64 | std::shared_ptr io = getIo(); 65 | ptr = std::make_shared(io, Private()); 66 | if (!ptr) 67 | { 68 | throw std::runtime_error("failed to create session manager"); 69 | } 70 | } 71 | return *ptr; 72 | } 73 | 74 | /** 75 | * @brief Start an IPMI session 76 | * 77 | * @param[in] remoteConsoleSessID - Remote Console Session ID mentioned 78 | * in the Open SessionRequest Command 79 | * @param[in] priv - Privilege level requested 80 | * @param[in] authAlgo - Authentication Algorithm 81 | * @param[in] intAlgo - Integrity Algorithm 82 | * @param[in] cryptAlgo - Confidentiality Algorithm 83 | * 84 | * @return session handle on success and nullptr on failure 85 | * 86 | */ 87 | std::shared_ptr startSession( 88 | SessionID remoteConsoleSessID, Privilege priv, 89 | cipher::rakp_auth::Algorithms authAlgo, 90 | cipher::integrity::Algorithms intAlgo, 91 | cipher::crypt::Algorithms cryptAlgo); 92 | 93 | /** 94 | * @brief Stop IPMI Session 95 | * 96 | * @param[in] bmcSessionID - BMC Session ID 97 | * 98 | * @return true on success and failure if session ID is invalid 99 | * 100 | */ 101 | bool stopSession(SessionID bmcSessionID); 102 | 103 | /** 104 | * @brief Get Session Handle 105 | * 106 | * @param[in] sessionID - Session ID 107 | * @param[in] option - Select between BMC Session ID and Remote Console 108 | * Session ID, Default option is BMC Session ID 109 | * 110 | * @return session handle on success and nullptr on failure 111 | * 112 | */ 113 | std::shared_ptr getSession( 114 | SessionID sessionID, 115 | RetrieveOption option = RetrieveOption::BMC_SESSION_ID); 116 | uint8_t getActiveSessionCount() const; 117 | uint8_t getSessionHandle(SessionID bmcSessionID) const; 118 | uint8_t storeSessionHandle(SessionID bmcSessionID); 119 | uint32_t getSessionIDbyHandle(uint8_t sessionHandle) const; 120 | 121 | void managerInit(const std::string& channel); 122 | 123 | uint8_t getNetworkInstance(void); 124 | 125 | /** 126 | * @brief Clean Session Stale Entries 127 | * 128 | * Schedules cleaning the inactive sessions entries from the Session Map 129 | */ 130 | void scheduleSessionCleaner(const std::chrono::microseconds& grace); 131 | 132 | private: 133 | /** 134 | * @brief reclaim system resources by limiting idle sessions 135 | * 136 | * Limits on active, authenticated sessions are calculated independently 137 | * from in-setup sessions, which are not required to be authenticated. This 138 | * will prevent would-be DoS attacks by calling a bunch of Open Session 139 | * requests to fill up all available sessions. Too many active sessions will 140 | * trigger a shorter timeout, but is unaffected by setup session counts. 141 | * 142 | * For active sessions, grace time is inversely proportional to (the number 143 | * of active sessions beyond max sessions per channel)^3 144 | * 145 | * For sessions in setup, grace time is inversely proportional to (the 146 | * number of total sessions beyond max sessions per channel)^3, with a max 147 | * of 3 seconds 148 | */ 149 | void cleanStaleEntries(); 150 | 151 | std::shared_ptr io; 152 | boost::asio::steady_timer timer; 153 | 154 | std::array sessionHandleMap = {0}; 155 | 156 | /** 157 | * @brief Session Manager keeps the session objects as a sorted 158 | * associative container with Session ID as the unique key 159 | */ 160 | SessionMap sessionsMap; 161 | std::unique_ptr objManager = nullptr; 162 | std::string chName{}; // Channel Name 163 | uint8_t ipmiNetworkInstance = 0; 164 | void setNetworkInstance(void); 165 | }; 166 | 167 | } // namespace session 168 | -------------------------------------------------------------------------------- /command_table.cpp: -------------------------------------------------------------------------------- 1 | #include "command_table.hpp" 2 | 3 | #include "main.hpp" 4 | #include "message_handler.hpp" 5 | #include "message_parsers.hpp" 6 | #include "sessions_manager.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace command 16 | { 17 | 18 | void Table::registerCommand(CommandID inCommand, std::unique_ptr&& entry) 19 | { 20 | auto& command = commandTable[inCommand.command]; 21 | 22 | if (command) 23 | { 24 | lg2::debug("Already Registered: {COMMAND}", "COMMAND", 25 | inCommand.command); 26 | return; 27 | } 28 | 29 | command = std::move(entry); 30 | } 31 | 32 | void Table::executeCommand(uint32_t inCommand, 33 | std::vector& commandData, 34 | std::shared_ptr handler) 35 | { 36 | using namespace std::chrono_literals; 37 | 38 | auto iterator = commandTable.find(inCommand); 39 | 40 | if (iterator == commandTable.end()) 41 | { 42 | CommandID command(inCommand); 43 | 44 | // Do not forward any session zero commands to ipmid 45 | if (handler->sessionID == session::sessionZero) 46 | { 47 | lg2::info( 48 | "Table: refuse to forward session-zero command: lun: {LUN}, netFn: {NETFN}, command: {COMMAND}", 49 | "LUN", command.lun(), "NETFN", command.netFn(), "COMMAND", 50 | command.cmd()); 51 | return; 52 | } 53 | std::shared_ptr session = 54 | session::Manager::get().getSession(handler->sessionID); 55 | 56 | // Ignore messages that are not part of an active session 57 | auto state = static_cast(session->state()); 58 | if (state != session::State::active) 59 | { 60 | return; 61 | } 62 | 63 | auto bus = getSdBus(); 64 | // forward the request onto the main ipmi queue 65 | using IpmiDbusRspType = std::tuple>; 67 | uint8_t lun = command.lun(); 68 | uint8_t netFn = command.netFn(); 69 | uint8_t cmd = command.cmd(); 70 | 71 | std::map options = { 72 | {"userId", ipmi::Value(static_cast( 73 | ipmi::ipmiUserGetUserId(session->userName)))}, 74 | {"privilege", 75 | ipmi::Value(static_cast(session->currentPrivilege()))}, 76 | {"currentSessionId", 77 | ipmi::Value(static_cast(session->getBMCSessionID()))}, 78 | }; 79 | bus->async_method_call( 80 | [handler](const boost::system::error_code& ec, 81 | const IpmiDbusRspType& response) { 82 | if (!ec) 83 | { 84 | const uint8_t& cc = std::get<3>(response); 85 | const std::vector& responseData = 86 | std::get<4>(response); 87 | std::vector payload; 88 | payload.reserve(1 + responseData.size()); 89 | payload.push_back(cc); 90 | payload.insert(payload.end(), responseData.begin(), 91 | responseData.end()); 92 | handler->outPayload = std::move(payload); 93 | } 94 | else 95 | { 96 | std::vector payload; 97 | payload.push_back(IPMI_CC_UNSPECIFIED_ERROR); 98 | handler->outPayload = std::move(payload); 99 | } 100 | }, 101 | "xyz.openbmc_project.Ipmi.Host", "/xyz/openbmc_project/Ipmi", 102 | "xyz.openbmc_project.Ipmi.Server", "execute", netFn, lun, cmd, 103 | commandData, options); 104 | } 105 | else 106 | { 107 | auto start = std::chrono::steady_clock::now(); 108 | 109 | // Ignore messages that are not part of an active/pre-active session 110 | if (handler->sessionID != session::sessionZero) 111 | { 112 | std::shared_ptr session = 113 | session::Manager::get().getSession(handler->sessionID); 114 | auto state = static_cast(session->state()); 115 | if ((state != session::State::setupInProgress) && 116 | (state != session::State::active)) 117 | { 118 | return; 119 | } 120 | } 121 | 122 | handler->outPayload = 123 | iterator->second->executeCommand(commandData, handler); 124 | 125 | auto end = std::chrono::steady_clock::now(); 126 | 127 | std::chrono::duration elapsedSeconds = 128 | std::chrono::duration_cast(end - start); 129 | 130 | // If command time execution time exceeds 2 seconds, log a time 131 | // exceeded message 132 | if (elapsedSeconds > 2s) 133 | { 134 | lg2::error("IPMI command timed out: {DELAY}", "DELAY", 135 | elapsedSeconds.count()); 136 | } 137 | } 138 | } 139 | 140 | std::vector NetIpmidEntry::executeCommand( 141 | std::vector& commandData, 142 | std::shared_ptr handler) 143 | { 144 | std::vector errResponse; 145 | 146 | // Check if the command qualifies to be run prior to establishing a session 147 | if (!sessionless && (handler->sessionID == session::sessionZero)) 148 | { 149 | errResponse.resize(1); 150 | errResponse[0] = IPMI_CC_INSUFFICIENT_PRIVILEGE; 151 | lg2::info( 152 | "Table: Insufficient privilege for command: lun: {LUN}, netFn: {NETFN}, command: {COMMAND}", 153 | "LUN", command.lun(), "NETFN", command.netFn(), "COMMAND", 154 | command.cmd()); 155 | return errResponse; 156 | } 157 | 158 | return functor(commandData, handler); 159 | } 160 | 161 | } // namespace command 162 | -------------------------------------------------------------------------------- /crypt_algo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace cipher 9 | { 10 | 11 | namespace crypt 12 | { 13 | 14 | /** 15 | * @enum Confidentiality Algorithms 16 | * 17 | * The Confidentiality Algorithm Number specifies the encryption/decryption 18 | * algorithm field that is used for encrypted payload data under the session. 19 | * The ‘encrypted’ bit in the payload type field being set identifies packets 20 | * with payloads that include data that is encrypted per this specification. 21 | * When payload data is encrypted, there may be additional “Confidentiality 22 | * Header” and/or “Confidentiality Trailer” fields that are included within the 23 | * payload. The size and definition of those fields is specific to the 24 | * particular confidentiality algorithm. Based on security recommendations 25 | * encrypting IPMI traffic is preferred, so NONE is not supported. 26 | */ 27 | enum class Algorithms : uint8_t 28 | { 29 | NONE, /**< No encryption (mandatory , not supported) */ 30 | AES_CBC_128, /**< AES-CBC-128 Algorithm (mandatory option) */ 31 | xRC4_128, /**< xRC4-128 Algorithm (optional option) */ 32 | xRC4_40, /**< xRC4-40 Algorithm (optional option) */ 33 | }; 34 | 35 | /** 36 | * @class Interface 37 | * 38 | * Interface is the base class for the Confidentiality Algorithms. 39 | */ 40 | class Interface 41 | { 42 | public: 43 | /** 44 | * @brief Constructor for Interface 45 | */ 46 | explicit Interface(const std::vector& k2) : k2(k2) {} 47 | 48 | Interface() = delete; 49 | virtual ~Interface() = default; 50 | Interface(const Interface&) = default; 51 | Interface& operator=(const Interface&) = default; 52 | Interface(Interface&&) = default; 53 | Interface& operator=(Interface&&) = default; 54 | 55 | /** 56 | * @brief Decrypt the incoming payload 57 | * 58 | * @param[in] packet - Incoming IPMI packet 59 | * @param[in] sessHeaderLen - Length of the IPMI Session Header 60 | * @param[in] payloadLen - Length of the encrypted IPMI payload 61 | * 62 | * @return decrypted payload if the operation is successful 63 | */ 64 | virtual std::vector decryptPayload( 65 | const std::vector& packet, const size_t sessHeaderLen, 66 | const size_t payloadLen) const = 0; 67 | 68 | /** 69 | * @brief Encrypt the outgoing payload 70 | * 71 | * @param[in] payload - plain payload for the outgoing IPMI packet 72 | * 73 | * @return encrypted payload if the operation is successful 74 | * 75 | */ 76 | virtual std::vector encryptPayload( 77 | std::vector& payload) const = 0; 78 | 79 | /** 80 | * @brief Check if the Confidentiality algorithm is supported 81 | * 82 | * @param[in] algo - confidentiality algorithm 83 | * 84 | * @return true if algorithm is supported else false 85 | * 86 | */ 87 | static bool isAlgorithmSupported(Algorithms algo) 88 | { 89 | if (algo == Algorithms::AES_CBC_128) 90 | { 91 | return true; 92 | } 93 | else 94 | { 95 | return false; 96 | } 97 | } 98 | 99 | protected: 100 | /** 101 | * @brief The Cipher Key is the first 128-bits of key “K2”, K2 is 102 | * generated by processing a pre-defined constant keyed by Session 103 | * Integrity Key (SIK) that was created during session activation. 104 | */ 105 | std::vector k2; 106 | }; 107 | 108 | /** 109 | * @class AlgoAES128 110 | * 111 | * @brief Implementation of the AES-CBC-128 Confidentiality algorithm 112 | * 113 | * AES-128 uses a 128-bit Cipher Key. The Cipher Key is the first 128-bits of 114 | * key “K2”.Once the Cipher Key has been generated it is used to encrypt 115 | * the payload data. The payload data is padded to make it an integral numbers 116 | * of blocks in length (a block is 16 bytes for AES). The payload is then 117 | * encrypted one block at a time from the lowest data offset to the highest 118 | * using Cipher_Key as specified in AES. 119 | */ 120 | class AlgoAES128 final : public Interface 121 | { 122 | public: 123 | static constexpr size_t AESCBC128ConfHeader = 16; 124 | static constexpr size_t AESCBC128BlockSize = 16; 125 | 126 | /** 127 | * If confidentiality bytes are present, the value of the first byte is 128 | * one (01h). and all subsequent bytes shall have a monotonically 129 | * increasing value (e.g., 02h, 03h, 04h, etc). The receiver, as an 130 | * additional check for proper decryption, shall check the value of each 131 | * byte of Confidentiality Pad. For AES algorithm, the pad bytes will 132 | * range from 0 to 15 bytes. This predefined array would help in 133 | * doing the additional check. 134 | */ 135 | static constexpr std::array confPadBytes = 136 | {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 137 | 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; 138 | 139 | /** 140 | * @brief Constructor for AlgoAES128 141 | * 142 | * @param[in] - Session Integrity key 143 | */ 144 | explicit AlgoAES128(const std::vector& k2) : Interface(k2) {} 145 | 146 | AlgoAES128() = delete; 147 | ~AlgoAES128() = default; 148 | AlgoAES128(const AlgoAES128&) = default; 149 | AlgoAES128& operator=(const AlgoAES128&) = default; 150 | AlgoAES128(AlgoAES128&&) = default; 151 | AlgoAES128& operator=(AlgoAES128&&) = default; 152 | 153 | /** 154 | * @brief Decrypt the incoming payload 155 | * 156 | * @param[in] packet - Incoming IPMI packet 157 | * @param[in] sessHeaderLen - Length of the IPMI Session Header 158 | * @param[in] payloadLen - Length of the encrypted IPMI payload 159 | * 160 | * @return decrypted payload if the operation is successful 161 | */ 162 | std::vector decryptPayload(const std::vector& packet, 163 | const size_t sessHeaderLen, 164 | const size_t payloadLen) const override; 165 | 166 | /** 167 | * @brief Encrypt the outgoing payload 168 | * 169 | * @param[in] payload - plain payload for the outgoing IPMI packet 170 | * 171 | * @return encrypted payload if the operation is successful 172 | * 173 | */ 174 | std::vector encryptPayload( 175 | std::vector& payload) const override; 176 | 177 | private: 178 | /** 179 | * @brief Decrypt the passed data 180 | * 181 | * @param[in] iv - Initialization vector 182 | * @param[in] input - Pointer to input data 183 | * @param[in] inputLen - Length of input data 184 | * 185 | * @return decrypted data if the operation is successful 186 | */ 187 | std::vector decryptData(const uint8_t* iv, const uint8_t* input, 188 | const int inputLen) const; 189 | 190 | /** 191 | * @brief Encrypt the passed data 192 | * 193 | * @param[in] input - Pointer to input data 194 | * @param[in] inputLen - Length of input data 195 | * 196 | * @return encrypted data if the operation is successful 197 | */ 198 | std::vector encryptData(const uint8_t* input, 199 | const int inputLen) const; 200 | }; 201 | 202 | } // namespace crypt 203 | 204 | } // namespace cipher 205 | -------------------------------------------------------------------------------- /message_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "message_handler.hpp" 2 | 3 | #include "command_table.hpp" 4 | #include "main.hpp" 5 | #include "message.hpp" 6 | #include "message_parsers.hpp" 7 | #include "sessions_manager.hpp" 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace message 18 | { 19 | 20 | bool Handler::receive() 21 | { 22 | std::vector packet; 23 | auto readStatus = 0; 24 | 25 | // Read the packet 26 | std::tie(readStatus, packet) = channel->read(); 27 | 28 | // Read of the packet failed 29 | if (readStatus < 0) 30 | { 31 | lg2::error("Error in Read status: {STATUS}", "STATUS", readStatus); 32 | return false; 33 | } 34 | 35 | // Unflatten the packet 36 | std::tie(inMessage, sessionHeader) = parser::unflatten(packet); 37 | 38 | return true; 39 | } 40 | 41 | void Handler::updSessionData(std::shared_ptr& inMessage) 42 | { 43 | session = session::Manager::get().getSession(inMessage->bmcSessionID); 44 | 45 | sessionID = inMessage->bmcSessionID; 46 | inMessage->rcSessionID = session->getRCSessionID(); 47 | session->updateLastTransactionTime(); 48 | session->channelPtr = channel; 49 | session->remotePort(channel->getPort()); 50 | uint32_t ipAddr = 0; 51 | channel->getRemoteAddress(ipAddr); 52 | session->remoteIPAddr(ipAddr); 53 | } 54 | 55 | Handler::~Handler() 56 | { 57 | try 58 | { 59 | #ifdef RMCP_PING 60 | if (inMessage && (ClassOfMsg::ASF == inMessage->rmcpMsgClass)) 61 | { 62 | sendASF(); 63 | } 64 | else 65 | #endif // RMCP_PING 66 | { 67 | if (outPayload) 68 | { 69 | std::shared_ptr outMessage = 70 | inMessage->createResponse(*outPayload); 71 | if (!outMessage) 72 | { 73 | return; 74 | } 75 | send(outMessage); 76 | } 77 | } 78 | } 79 | catch (const std::exception& e) 80 | { 81 | // send failed, most likely due to a session closure 82 | lg2::info("Async RMCP+ reply failed: {ERROR}", "ERROR", e); 83 | } 84 | } 85 | 86 | void Handler::processIncoming() 87 | { 88 | // Read the incoming IPMI packet 89 | if (!receive()) 90 | { 91 | return; 92 | } 93 | 94 | #ifdef RMCP_PING 95 | // Execute the Command, possibly asynchronously 96 | if (inMessage && (ClassOfMsg::ASF != inMessage->rmcpMsgClass)) 97 | #endif // RMCP_PING 98 | { 99 | updSessionData(inMessage); 100 | executeCommand(); 101 | } 102 | 103 | // send happens during the destructor if a payload was set 104 | } 105 | 106 | void Handler::executeCommand() 107 | { 108 | // Get the CommandID to map into the command table 109 | auto command = inMessage->getCommand(); 110 | if (inMessage->payloadType == PayloadType::IPMI) 111 | { 112 | // Process PayloadType::IPMI only if ipmi is enabled or for sessionless 113 | // or for session establisbment command 114 | if (this->sessionID == session::sessionZero || 115 | session->sessionUserPrivAccess.ipmiEnabled) 116 | { 117 | if (inMessage->payload.size() < 118 | (sizeof(LAN::header::Request) + sizeof(LAN::trailer::Request))) 119 | { 120 | return; 121 | } 122 | 123 | auto start = inMessage->payload.begin() + 124 | sizeof(LAN::header::Request); 125 | auto end = inMessage->payload.end() - sizeof(LAN::trailer::Request); 126 | std::vector inPayload(start, end); 127 | command::Table::get().executeCommand(command, inPayload, 128 | shared_from_this()); 129 | } 130 | else 131 | { 132 | std::vector payload{IPMI_CC_INSUFFICIENT_PRIVILEGE}; 133 | outPayload = std::move(payload); 134 | } 135 | } 136 | else 137 | { 138 | command::Table::get().executeCommand(command, inMessage->payload, 139 | shared_from_this()); 140 | } 141 | } 142 | 143 | void Handler::writeData(const std::vector& packet) 144 | { 145 | auto writeStatus = channel->write(packet); 146 | if (writeStatus < 0) 147 | { 148 | throw std::runtime_error("Error in writing to socket"); 149 | } 150 | } 151 | 152 | #ifdef RMCP_PING 153 | void Handler::sendASF() 154 | { 155 | // Flatten the packet 156 | auto packet = asfparser::flatten(inMessage->asfMsgTag); 157 | 158 | // Write the packet 159 | writeData(packet); 160 | } 161 | #endif // RMCP_PING 162 | 163 | void Handler::send(std::shared_ptr outMessage) 164 | { 165 | // Flatten the packet 166 | auto packet = parser::flatten(outMessage, sessionHeader, session); 167 | 168 | // Write the packet 169 | writeData(packet); 170 | } 171 | 172 | void Handler::setChannelInSession() const 173 | { 174 | session->channelPtr = channel; 175 | } 176 | 177 | void Handler::sendSOLPayload(const std::vector& input) 178 | { 179 | auto outMessage = std::make_shared(); 180 | outMessage->payloadType = PayloadType::SOL; 181 | outMessage->payload = input; 182 | outMessage->isPacketEncrypted = session->isCryptAlgoEnabled(); 183 | outMessage->isPacketAuthenticated = session->isIntegrityAlgoEnabled(); 184 | outMessage->rcSessionID = session->getRCSessionID(); 185 | outMessage->bmcSessionID = sessionID; 186 | 187 | send(outMessage); 188 | } 189 | 190 | void Handler::sendUnsolicitedIPMIPayload(uint8_t netfn, uint8_t cmd, 191 | const std::vector& output) 192 | { 193 | auto outMessage = std::make_shared(); 194 | outMessage->payloadType = PayloadType::IPMI; 195 | outMessage->isPacketEncrypted = session->isCryptAlgoEnabled(); 196 | outMessage->isPacketAuthenticated = session->isIntegrityAlgoEnabled(); 197 | outMessage->rcSessionID = session->getRCSessionID(); 198 | outMessage->bmcSessionID = sessionID; 199 | 200 | outMessage->payload.resize(sizeof(LAN::header::Request) + output.size() + 201 | sizeof(LAN::trailer::Request)); 202 | 203 | auto respHeader = 204 | reinterpret_cast(outMessage->payload.data()); 205 | 206 | // Add IPMI LAN Message Request Header 207 | respHeader->rsaddr = LAN::requesterBMCAddress; 208 | respHeader->netfn = (netfn << 0x02); 209 | respHeader->cs = crc8bit(&(respHeader->rsaddr), 2); 210 | respHeader->rqaddr = LAN::responderBMCAddress; 211 | respHeader->rqseq = 0; 212 | respHeader->cmd = cmd; 213 | 214 | auto assembledSize = sizeof(LAN::header::Request); 215 | 216 | // Copy the output by the execution of the command 217 | std::copy(output.begin(), output.end(), 218 | outMessage->payload.begin() + assembledSize); 219 | assembledSize += output.size(); 220 | 221 | // Add the IPMI LAN Message Trailer 222 | auto trailer = reinterpret_cast( 223 | outMessage->payload.data() + assembledSize); 224 | 225 | // Calculate the checksum for the field rqaddr in the header to the 226 | // command data, 3 corresponds to size of the fields before rqaddr( rsaddr, 227 | // netfn, cs). 228 | trailer->checksum = crc8bit(&respHeader->rqaddr, assembledSize - 3); 229 | 230 | send(outMessage); 231 | } 232 | 233 | } // namespace message 234 | -------------------------------------------------------------------------------- /auth_algo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "crypt_algo.hpp" 4 | #include "integrity_algo.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace cipher 12 | { 13 | namespace rakp_auth 14 | { 15 | constexpr size_t USER_KEY_MAX_LENGTH = 20; 16 | constexpr size_t BMC_RANDOM_NUMBER_LEN = 16; 17 | constexpr size_t REMOTE_CONSOLE_RANDOM_NUMBER_LEN = 16; 18 | 19 | /** 20 | * @enum RAKP Authentication Algorithms 21 | * 22 | * RMCP+ Authenticated Key-Exchange Protocol (RAKP) 23 | * 24 | * RAKP-None is not supported as per the following recommendation 25 | * (https://www.us-cert.gov/ncas/alerts/TA13-207A) 26 | * ("cipher 0" is an option enabled by default on many IPMI enabled devices that 27 | * allows authentication to be bypassed. Disable "cipher 0" to prevent 28 | * attackers from bypassing authentication and sending arbitrary IPMI commands.) 29 | */ 30 | enum class Algorithms : uint8_t 31 | { 32 | RAKP_NONE = 0, // Mandatory (implemented, not supported) 33 | RAKP_HMAC_SHA1, // Mandatory (implemented, default choice in ipmitool) 34 | RAKP_HMAC_MD5, // Optional (not implemented) 35 | RAKP_HMAC_SHA256, // Optional (implemented, best available) 36 | // Reserved used to indicate an invalid authentication algorithm 37 | RAKP_HMAC_INVALID = 0xB0 38 | }; 39 | 40 | /** 41 | * @class Interface 42 | * 43 | * Interface is the base class for the Authentication Algorithms. 44 | * The Authentication Algorithm specifies the type of authentication “handshake” 45 | * process that is used and identifies any particular variations of hashing or 46 | * signature algorithm that is used as part of the process. 47 | * 48 | */ 49 | class Interface 50 | { 51 | public: 52 | explicit Interface(integrity::Algorithms intAlgo, 53 | crypt::Algorithms cryptAlgo) : 54 | intAlgo(intAlgo), cryptAlgo(cryptAlgo) 55 | {} 56 | 57 | Interface() = delete; 58 | virtual ~Interface() = default; 59 | Interface(const Interface&) = default; 60 | Interface& operator=(const Interface&) = default; 61 | Interface(Interface&&) = default; 62 | Interface& operator=(Interface&&) = default; 63 | 64 | /** 65 | * @brief Generate the Hash Message Authentication Code 66 | * 67 | * This API is invoked to generate the Key Exchange Authentication Code 68 | * in the RAKP2 and RAKP4 sequence and for generating the Session 69 | * Integrity Key. 70 | * 71 | * @param input message 72 | * 73 | * @return hash output 74 | * 75 | * @note The user key which is the secret key for the hash operation 76 | * needs to be set before this operation. 77 | */ 78 | std::vector virtual generateHMAC( 79 | const std::vector& input) const = 0; 80 | 81 | /** 82 | * @brief Generate the Integrity Check Value 83 | * 84 | * This API is invoked in the RAKP4 sequence for generating the 85 | * Integrity Check Value. 86 | * 87 | * @param input message 88 | * 89 | * @return hash output 90 | * 91 | * @note The session integrity key which is the secret key for the 92 | * hash operation needs to be set before this operation. 93 | */ 94 | std::vector virtual generateICV( 95 | const std::vector& input) const = 0; 96 | 97 | /** 98 | * @brief Check if the Authentication algorithm is supported 99 | * 100 | * @param[in] algo - authentication algorithm 101 | * 102 | * @return true if algorithm is supported else false 103 | * 104 | */ 105 | static bool isAlgorithmSupported(Algorithms algo) 106 | { 107 | if (algo == Algorithms::RAKP_HMAC_SHA256) 108 | { 109 | return true; 110 | } 111 | else 112 | { 113 | return false; 114 | } 115 | } 116 | 117 | // User Key is hardcoded to PASSW0RD till the IPMI User account 118 | // management is in place. 119 | std::array userKey = {"0penBmc"}; 120 | 121 | // Managed System Random Number 122 | std::array bmcRandomNum; 123 | 124 | // Remote Console Random Number 125 | std::array rcRandomNum; 126 | 127 | // Session Integrity Key 128 | std::vector sessionIntegrityKey; 129 | 130 | /** 131 | * Integrity Algorithm is activated and set in the session data only 132 | * once the session setup is succeeded in the RAKP34 command. But the 133 | * integrity algorithm is negotiated in the Open Session Request command 134 | * . So the integrity algorithm successfully negotiated is stored 135 | * in the authentication algorithm's instance. 136 | */ 137 | integrity::Algorithms intAlgo; 138 | 139 | /** 140 | * Confidentiality Algorithm is activated and set in the session data 141 | * only once the session setup is succeeded in the RAKP34 command. But 142 | * the confidentiality algorithm is negotiated in the Open Session 143 | * Request command. So the confidentiality algorithm successfully 144 | * negotiated is stored in the authentication algorithm's instance. 145 | */ 146 | crypt::Algorithms cryptAlgo; 147 | }; 148 | 149 | /** 150 | * @class AlgoSHA1 151 | * 152 | * RAKP-HMAC-SHA1 specifies the use of RAKP messages for the key exchange 153 | * portion of establishing the session, and that HMAC-SHA1 (per [RFC2104]) is 154 | * used to create 20-byte Key Exchange Authentication Code fields in RAKP 155 | * Message 2 and RAKP Message 3. HMAC-SHA1-96(per [RFC2404]) is used for 156 | * generating a 12-byte Integrity Check Value field for RAKP Message 4. 157 | */ 158 | 159 | class AlgoSHA1 : public Interface 160 | { 161 | public: 162 | static constexpr size_t integrityCheckValueLength = 12; 163 | 164 | explicit AlgoSHA1(integrity::Algorithms intAlgo, 165 | crypt::Algorithms cryptAlgo) : 166 | Interface(intAlgo, cryptAlgo) 167 | {} 168 | 169 | AlgoSHA1() = delete; 170 | ~AlgoSHA1() = default; 171 | AlgoSHA1(const AlgoSHA1&) = default; 172 | AlgoSHA1& operator=(const AlgoSHA1&) = default; 173 | AlgoSHA1(AlgoSHA1&&) = default; 174 | AlgoSHA1& operator=(AlgoSHA1&&) = default; 175 | 176 | std::vector generateHMAC( 177 | const std::vector& input) const override; 178 | 179 | std::vector generateICV( 180 | const std::vector& input) const override; 181 | }; 182 | 183 | /** 184 | * @class AlgoSHA256 185 | * 186 | * RAKP-HMAC-SHA256 specifies the use of RAKP messages for the key exchange 187 | * portion of establishing the session, and that HMAC-SHA256 (per [FIPS 180-2] 188 | * and [RFC4634] and is used to create a 32-byte Key Exchange Authentication 189 | * Code fields in RAKP Message 2 and RAKP Message 3. HMAC-SHA256-128 (per 190 | * [RFC4868]) is used for generating a 16-byte Integrity Check Value field for 191 | * RAKP Message 4. 192 | */ 193 | 194 | class AlgoSHA256 : public Interface 195 | { 196 | public: 197 | static constexpr size_t integrityCheckValueLength = 16; 198 | 199 | explicit AlgoSHA256(integrity::Algorithms intAlgo, 200 | crypt::Algorithms cryptAlgo) : 201 | Interface(intAlgo, cryptAlgo) 202 | {} 203 | 204 | ~AlgoSHA256() = default; 205 | AlgoSHA256(const AlgoSHA256&) = default; 206 | AlgoSHA256& operator=(const AlgoSHA256&) = default; 207 | AlgoSHA256(AlgoSHA256&&) = default; 208 | AlgoSHA256& operator=(AlgoSHA256&&) = default; 209 | 210 | std::vector generateHMAC( 211 | const std::vector& input) const override; 212 | 213 | std::vector generateICV( 214 | const std::vector& input) const override; 215 | }; 216 | 217 | } // namespace rakp_auth 218 | 219 | } // namespace cipher 220 | -------------------------------------------------------------------------------- /crypt_algo.cpp: -------------------------------------------------------------------------------- 1 | #include "crypt_algo.hpp" 2 | 3 | #include "message_parsers.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace cipher 13 | { 14 | 15 | namespace crypt 16 | { 17 | 18 | constexpr std::array 19 | AlgoAES128::confPadBytes; 20 | 21 | std::vector AlgoAES128::decryptPayload( 22 | const std::vector& packet, const size_t sessHeaderLen, 23 | const size_t payloadLen) const 24 | { 25 | // verify packet size minimal: sessHeaderLen + payloadLen 26 | // and payloadLen is more than AESCBC128ConfHeader 27 | if (packet.size() < (sessHeaderLen + payloadLen) || 28 | payloadLen < AESCBC128ConfHeader) 29 | { 30 | throw std::runtime_error("Invalid data length"); 31 | } 32 | 33 | auto plainPayload = 34 | decryptData(packet.data() + sessHeaderLen, 35 | packet.data() + sessHeaderLen + AESCBC128ConfHeader, 36 | payloadLen - AESCBC128ConfHeader); 37 | 38 | /* 39 | * The confidentiality pad length is the last byte in the payload, it would 40 | * tell the number of pad bytes in the payload. We added a condition, so 41 | * that buffer overrun doesn't happen. 42 | */ 43 | size_t confPadLength = plainPayload.back(); 44 | auto padLength = std::min(plainPayload.size() - 1, confPadLength); 45 | 46 | auto plainPayloadLen = plainPayload.size() - padLength - 1; 47 | 48 | // Additional check if the confidentiality pad bytes are as expected 49 | if (!std::equal(plainPayload.begin() + plainPayloadLen, 50 | plainPayload.begin() + plainPayloadLen + padLength, 51 | confPadBytes.begin())) 52 | { 53 | throw std::runtime_error("Confidentiality pad bytes check failed"); 54 | } 55 | 56 | plainPayload.resize(plainPayloadLen); 57 | 58 | return plainPayload; 59 | } 60 | 61 | std::vector AlgoAES128::encryptPayload( 62 | std::vector& payload) const 63 | { 64 | auto payloadLen = payload.size(); 65 | 66 | /* 67 | * The following logic calculates the number of padding bytes to be added to 68 | * the payload data. This would ensure that the length is a multiple of the 69 | * block size of algorithm being used. For the AES algorithm, the block size 70 | * is 16 bytes. 71 | */ 72 | auto paddingLen = AESCBC128BlockSize - ((payloadLen + 1) & 0xF); 73 | 74 | /* 75 | * The additional field is for the Confidentiality Pad Length field. For the 76 | * AES algorithm, this number will range from 0 to 15 bytes. This field is 77 | * mandatory. 78 | */ 79 | payload.resize(payloadLen + paddingLen + 1); 80 | 81 | /* 82 | * If no Confidentiality Pad bytes are required, the Confidentiality Pad 83 | * Length field is set to 00h. If present, the value of the first byte of 84 | * Confidentiality Pad shall be one (01h) and all subsequent bytes shall 85 | * have a monotonically increasing value (e.g., 02h, 03h, 04h, etc). 86 | */ 87 | if (0 != paddingLen) 88 | { 89 | std::iota(payload.begin() + payloadLen, 90 | payload.begin() + payloadLen + paddingLen, 1); 91 | } 92 | 93 | payload.back() = paddingLen; 94 | 95 | return encryptData(payload.data(), payload.size()); 96 | } 97 | 98 | std::vector AlgoAES128::decryptData( 99 | const uint8_t* iv, const uint8_t* input, const int inputLen) const 100 | { 101 | // Initializes Cipher context 102 | EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); 103 | 104 | auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); }; 105 | 106 | std::unique_ptr ctxPtr( 107 | ctx, cleanupFunc); 108 | 109 | /* 110 | * EVP_DecryptInit_ex sets up cipher context ctx for encryption with type 111 | * AES-CBC-128. ctx must be initialized before calling this function. K2 is 112 | * the symmetric key used and iv is the initialization vector used. 113 | */ 114 | if (!EVP_DecryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(), 115 | iv)) 116 | { 117 | throw std::runtime_error("EVP_DecryptInit_ex failed for type " 118 | "AES-CBC-128"); 119 | } 120 | 121 | /* 122 | * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad 123 | * parameter is zero then no padding is performed. This function always 124 | * returns 1. 125 | */ 126 | EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0); 127 | 128 | std::vector output(inputLen + AESCBC128BlockSize); 129 | 130 | int outputLen = 0; 131 | 132 | /* 133 | * If padding is disabled then EVP_DecryptFinal_ex() will not encrypt any 134 | * more data and it will return an error if any data remains in a partial 135 | * block: that is if the total data length is not a multiple of the block 136 | * size. Since AES-CBC-128 encrypted payload format adds padding bytes and 137 | * ensures that payload is a multiple of block size, we are not making the 138 | * call to EVP_DecryptFinal_ex(). 139 | */ 140 | if (!EVP_DecryptUpdate(ctxPtr.get(), output.data(), &outputLen, input, 141 | inputLen)) 142 | { 143 | throw std::runtime_error("EVP_DecryptUpdate failed"); 144 | } 145 | 146 | output.resize(outputLen); 147 | 148 | return output; 149 | } 150 | 151 | std::vector AlgoAES128::encryptData(const uint8_t* input, 152 | const int inputLen) const 153 | { 154 | std::vector output(inputLen + AESCBC128BlockSize); 155 | 156 | // Generate the initialization vector 157 | if (!RAND_bytes(output.data(), AESCBC128ConfHeader)) 158 | { 159 | throw std::runtime_error("RAND_bytes failed"); 160 | } 161 | 162 | // Initializes Cipher context 163 | EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); 164 | 165 | auto cleanupFunc = [](EVP_CIPHER_CTX* ctx) { EVP_CIPHER_CTX_free(ctx); }; 166 | 167 | std::unique_ptr ctxPtr( 168 | ctx, cleanupFunc); 169 | 170 | /* 171 | * EVP_EncryptInit_ex sets up cipher context ctx for encryption with type 172 | * AES-CBC-128. ctx must be initialized before calling this function. K2 is 173 | * the symmetric key used and iv is the initialization vector used. 174 | */ 175 | if (!EVP_EncryptInit_ex(ctxPtr.get(), EVP_aes_128_cbc(), NULL, k2.data(), 176 | output.data())) 177 | { 178 | throw std::runtime_error("EVP_EncryptInit_ex failed for type " 179 | "AES-CBC-128"); 180 | } 181 | 182 | /* 183 | * EVP_CIPHER_CTX_set_padding() enables or disables padding. If the pad 184 | * parameter is zero then no padding is performed. This function always 185 | * returns 1. 186 | */ 187 | EVP_CIPHER_CTX_set_padding(ctxPtr.get(), 0); 188 | 189 | int outputLen = 0; 190 | 191 | /* 192 | * If padding is disabled then EVP_EncryptFinal_ex() will not encrypt any 193 | * more data and it will return an error if any data remains in a partial 194 | * block: that is if the total data length is not a multiple of the block 195 | * size. Since we are adding padding bytes and ensures that payload is a 196 | * multiple of block size, we are not making the call to 197 | * EVP_DecryptFinal_ex() 198 | */ 199 | if (!EVP_EncryptUpdate(ctxPtr.get(), output.data() + AESCBC128ConfHeader, 200 | &outputLen, input, inputLen)) 201 | { 202 | throw std::runtime_error("EVP_EncryptUpdate failed for type " 203 | "AES-CBC-128"); 204 | } 205 | 206 | output.resize(AESCBC128ConfHeader + outputLen); 207 | 208 | return output; 209 | } 210 | 211 | } // namespace crypt 212 | 213 | } // namespace cipher 214 | -------------------------------------------------------------------------------- /command_table.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message_handler.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace command 12 | { 13 | 14 | struct CommandID 15 | { 16 | static constexpr size_t lunBits = 2; 17 | CommandID(uint32_t command) : command(command) {} 18 | 19 | uint8_t netFnLun() const 20 | { 21 | return static_cast(command >> CHAR_BIT); 22 | } 23 | uint8_t netFn() const 24 | { 25 | return netFnLun() >> lunBits; 26 | } 27 | uint8_t lun() const 28 | { 29 | return netFnLun() & ((1 << (lunBits + 1)) - 1); 30 | } 31 | uint8_t cmd() const 32 | { 33 | return static_cast(command); 34 | } 35 | uint32_t command; 36 | }; 37 | 38 | /** 39 | * CommandFunctor is the functor register for commands defined in 40 | * phosphor-net-ipmid. This would take the request part of the command as a 41 | * vector and a reference to the message handler. The response part of the 42 | * command is returned as a vector. 43 | */ 44 | using CommandFunctor = std::function( 45 | const std::vector&, std::shared_ptr&)>; 46 | 47 | /** 48 | * @struct CmdDetails 49 | * 50 | * Command details is used to register commands supported in phosphor-net-ipmid. 51 | */ 52 | struct CmdDetails 53 | { 54 | CommandID command; 55 | CommandFunctor functor; 56 | session::Privilege privilege; 57 | bool sessionless; 58 | }; 59 | 60 | /** 61 | * @enum NetFns 62 | * 63 | * A field that identifies the functional class of the message. The Network 64 | * Function clusters IPMI commands into different sets. 65 | */ 66 | enum class NetFns 67 | { 68 | CHASSIS = (0x00 << 10), 69 | CHASSIS_RESP = (0x01 << 10), 70 | 71 | BRIDGE = (0x02 << 10), 72 | BRIDGE_RESP = (0x03 << 10), 73 | 74 | SENSOR = (0x04 << 10), 75 | SENSOR_RESP = (0x05 << 10), 76 | EVENT = (0x04 << 10), 77 | EVENT_RESP = (0x05 << 10), 78 | 79 | APP = (0x06 << 10), 80 | APP_RESP = (0x07 << 10), 81 | 82 | FIRMWARE = (0x08 << 10), 83 | FIRMWARE_RESP = (0x09 << 10), 84 | 85 | STORAGE = (0x0A << 10), 86 | STORAGE_RESP = (0x0B << 10), 87 | 88 | TRANSPORT = (0x0C << 10), 89 | TRANSPORT_RESP = (0x0D << 10), 90 | 91 | //>> 92 | RESERVED_START = (0x0E << 10), 93 | RESERVED_END = (0x2B << 10), 94 | //<< 95 | 96 | GROUP_EXTN = (0x2C << 10), 97 | GROUP_EXTN_RESP = (0x2D << 10), 98 | 99 | OEM = (0x2E << 10), 100 | OEM_RESP = (0x2F << 10), 101 | }; 102 | 103 | /** 104 | * @class Entry 105 | * 106 | * This is the base class for registering IPMI commands. There are two ways of 107 | * registering commands to phosphor-net-ipmid, the session related commands and 108 | * provider commands 109 | * 110 | * Every commands has a privilege level which mentions the minimum session 111 | * privilege level needed to execute the command 112 | */ 113 | 114 | class Entry 115 | { 116 | public: 117 | Entry(CommandID command, session::Privilege privilege) : 118 | command(command), privilege(privilege) 119 | {} 120 | 121 | /** 122 | * @brief Execute the command 123 | * 124 | * Execute the command 125 | * 126 | * @param[in] commandData - Request Data for the command 127 | * @param[in] handler - Reference to the Message Handler 128 | * 129 | * @return Response data for the command 130 | */ 131 | virtual std::vector executeCommand( 132 | std::vector& commandData, 133 | std::shared_ptr handler) = 0; 134 | 135 | auto getCommand() const 136 | { 137 | return command; 138 | } 139 | 140 | auto getPrivilege() const 141 | { 142 | return privilege; 143 | } 144 | 145 | virtual ~Entry() = default; 146 | Entry(const Entry&) = default; 147 | Entry& operator=(const Entry&) = default; 148 | Entry(Entry&&) = default; 149 | Entry& operator=(Entry&&) = default; 150 | 151 | protected: 152 | CommandID command; 153 | 154 | // Specifies the minimum privilege level required to execute this command 155 | session::Privilege privilege; 156 | }; 157 | 158 | /** 159 | * @class NetIpmidEntry 160 | * 161 | * NetIpmidEntry is used to register commands that are consumed only in 162 | * phosphor-net-ipmid. The RAKP commands, session commands and user management 163 | * commands are examples of this. 164 | * 165 | * There are certain IPMI commands that can be executed before session can be 166 | * established like Get System GUID, Get Channel Authentication Capabilities 167 | * and RAKP commands. 168 | */ 169 | class NetIpmidEntry final : public Entry 170 | { 171 | public: 172 | NetIpmidEntry(CommandID command, CommandFunctor functor, 173 | session::Privilege privilege, bool sessionless) : 174 | Entry(command, privilege), functor(functor), sessionless(sessionless) 175 | {} 176 | 177 | /** 178 | * @brief Execute the command 179 | * 180 | * Execute the command 181 | * 182 | * @param[in] commandData - Request Data for the command 183 | * @param[in] handler - Reference to the Message Handler 184 | * 185 | * @return Response data for the command 186 | */ 187 | std::vector executeCommand( 188 | std::vector& commandData, 189 | std::shared_ptr handler) override; 190 | 191 | virtual ~NetIpmidEntry() = default; 192 | NetIpmidEntry(const NetIpmidEntry&) = default; 193 | NetIpmidEntry& operator=(const NetIpmidEntry&) = default; 194 | NetIpmidEntry(NetIpmidEntry&&) = default; 195 | NetIpmidEntry& operator=(NetIpmidEntry&&) = default; 196 | 197 | private: 198 | CommandFunctor functor; 199 | 200 | bool sessionless; 201 | }; 202 | 203 | /** 204 | * @class Table 205 | * 206 | * Table keeps the IPMI command entries as a sorted associative container with 207 | * Command ID as the unique key. It has interfaces for registering commands 208 | * and executing a command. 209 | */ 210 | class Table 211 | { 212 | private: 213 | struct Private 214 | {}; 215 | 216 | public: 217 | explicit Table(const Private&) {} 218 | Table() = delete; 219 | ~Table() = default; 220 | // Command Table is a singleton so copy, copy-assignment, move and 221 | // move assignment is deleted 222 | Table(const Table&) = delete; 223 | Table& operator=(const Table&) = delete; 224 | Table(Table&&) = default; 225 | Table& operator=(Table&&) = default; 226 | 227 | /** 228 | * @brief Get a reference to the singleton Table 229 | * 230 | * @return Table reference 231 | */ 232 | static Table& get() 233 | { 234 | static std::shared_ptr ptr = nullptr; 235 | if (!ptr) 236 | { 237 | ptr = std::make_shared
(Private()); 238 | } 239 | return *ptr; 240 | } 241 | 242 | using CommandTable = std::map>; 243 | 244 | /** 245 | * @brief Register a command 246 | * 247 | * Register a command with the command table 248 | * 249 | * @param[in] inCommand - Command ID 250 | * @param[in] entry - Command Entry 251 | * 252 | * @return: None 253 | * 254 | * @note: Duplicate registrations will be rejected. 255 | * 256 | */ 257 | void registerCommand(CommandID inCommand, std::unique_ptr&& entry); 258 | 259 | /** 260 | * @brief Execute the command 261 | * 262 | * Execute the command for the corresponding CommandID 263 | * 264 | * @param[in] inCommand - Command ID to execute. 265 | * @param[in] commandData - Request Data for the command 266 | * @param[in] handler - Reference to the Message Handler 267 | * 268 | */ 269 | void executeCommand(uint32_t inCommand, std::vector& commandData, 270 | std::shared_ptr handler); 271 | 272 | private: 273 | CommandTable commandTable; 274 | }; 275 | 276 | } // namespace command 277 | -------------------------------------------------------------------------------- /sd_event_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "sd_event_loop.hpp" 2 | 3 | #include "main.hpp" 4 | #include "message_handler.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace eventloop 18 | { 19 | 20 | void EventLoop::handleRmcpPacket() 21 | { 22 | try 23 | { 24 | auto channelPtr = std::make_shared(udpSocket); 25 | 26 | // Initialize the Message Handler with the socket channel 27 | auto msgHandler = std::make_shared(channelPtr, io); 28 | 29 | msgHandler->processIncoming(); 30 | } 31 | catch (const std::exception& e) 32 | { 33 | lg2::error("Executing the IPMI message failed: {ERROR}", "ERROR", e); 34 | } 35 | } 36 | 37 | void EventLoop::startRmcpReceive() 38 | { 39 | udpSocket->async_wait( 40 | boost::asio::socket_base::wait_read, 41 | [this](const boost::system::error_code& ec) { 42 | if (!ec) 43 | { 44 | boost::asio::post(*io, [this]() { startRmcpReceive(); }); 45 | handleRmcpPacket(); 46 | } 47 | }); 48 | } 49 | 50 | int EventLoop::getVLANID(const std::string channel) 51 | { 52 | int vlanid = 0; 53 | if (channel.empty()) 54 | { 55 | return 0; 56 | } 57 | 58 | sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 59 | // Enumerate all VLAN + ETHERNET interfaces 60 | auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, 61 | "GetSubTree"); 62 | req.append(PATH_ROOT, 0, 63 | std::vector{INTF_VLAN, INTF_ETHERNET}); 64 | ObjectTree objs; 65 | try 66 | { 67 | auto reply = bus.call(req); 68 | reply.read(objs); 69 | } 70 | catch (const std::exception& e) 71 | { 72 | lg2::error("getVLANID: failed to execute/read GetSubTree: {ERROR}", 73 | "ERROR", e); 74 | return 0; 75 | } 76 | 77 | std::string ifService, logicalPath; 78 | for (const auto& [path, impls] : objs) 79 | { 80 | if (path.find(channel) == path.npos) 81 | { 82 | continue; 83 | } 84 | for (const auto& [service, intfs] : impls) 85 | { 86 | bool vlan = false; 87 | bool ethernet = false; 88 | for (const auto& intf : intfs) 89 | { 90 | if (intf == INTF_VLAN) 91 | { 92 | vlan = true; 93 | } 94 | else if (intf == INTF_ETHERNET) 95 | { 96 | ethernet = true; 97 | } 98 | } 99 | if (ifService.empty() && (vlan || ethernet)) 100 | { 101 | ifService = service; 102 | } 103 | if (logicalPath.empty() && vlan) 104 | { 105 | logicalPath = path; 106 | } 107 | } 108 | } 109 | 110 | // VLAN devices will always have a separate logical object 111 | if (logicalPath.empty()) 112 | { 113 | return 0; 114 | } 115 | 116 | Value value; 117 | auto method = bus.new_method_call(ifService.c_str(), logicalPath.c_str(), 118 | PROP_INTF, METHOD_GET); 119 | method.append(INTF_VLAN, "Id"); 120 | try 121 | { 122 | auto method_reply = bus.call(method); 123 | method_reply.read(value); 124 | } 125 | catch (const std::exception& e) 126 | { 127 | lg2::error("getVLANID: failed to execute/read VLAN Id: {ERROR}", 128 | "ERROR", e); 129 | return 0; 130 | } 131 | 132 | vlanid = std::get(value); 133 | if ((vlanid & VLAN_VALUE_MASK) != vlanid) 134 | { 135 | lg2::error("networkd returned an invalid vlan: {VLAN}", "VLAN", vlanid); 136 | return 0; 137 | } 138 | 139 | return vlanid; 140 | } 141 | 142 | int EventLoop::setupSocket(std::shared_ptr& bus, 143 | std::string channel, uint16_t reqPort) 144 | { 145 | std::string iface = channel; 146 | static constexpr const char* unboundIface = "rmcpp"; 147 | if (channel == "") 148 | { 149 | iface = channel = unboundIface; 150 | } 151 | else 152 | { 153 | // If VLANID of this channel is set, bind the socket to this 154 | // VLAN logic device 155 | auto vlanid = getVLANID(channel); 156 | if (vlanid) 157 | { 158 | iface = iface + "." + std::to_string(vlanid); 159 | lg2::debug("This channel has VLAN id: {VLAN}", "VLAN", vlanid); 160 | } 161 | } 162 | // Create our own socket if SysD did not supply one. 163 | int listensFdCount = sd_listen_fds(0); 164 | if (listensFdCount > 1) 165 | { 166 | lg2::error("Too many file descriptors received, listensFdCount: {FD}", 167 | "FD", listensFdCount); 168 | return EXIT_FAILURE; 169 | } 170 | if (listensFdCount == 1) 171 | { 172 | int openFd = SD_LISTEN_FDS_START; 173 | if (!sd_is_socket(openFd, AF_UNSPEC, SOCK_DGRAM, -1)) 174 | { 175 | lg2::error("Failed to set up systemd-passed socket: {ERROR}", 176 | "ERROR", strerror(errno)); 177 | return EXIT_FAILURE; 178 | } 179 | udpSocket = std::make_shared( 180 | *io, boost::asio::ip::udp::v6(), openFd); 181 | } 182 | else 183 | { 184 | // asio does not natively offer a way to bind to an interface 185 | // so it must be done in steps 186 | boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v6(), reqPort); 187 | udpSocket = std::make_shared(*io); 188 | udpSocket->open(ep.protocol()); 189 | // bind 190 | udpSocket->set_option( 191 | boost::asio::ip::udp::socket::reuse_address(true)); 192 | udpSocket->bind(ep); 193 | } 194 | // SO_BINDTODEVICE 195 | char nameout[IFNAMSIZ]; 196 | unsigned int lenout = sizeof(nameout); 197 | if ((::getsockopt(udpSocket->native_handle(), SOL_SOCKET, SO_BINDTODEVICE, 198 | nameout, &lenout) == -1)) 199 | { 200 | lg2::error("Failed to read bound device: {ERROR}", "ERROR", 201 | strerror(errno)); 202 | } 203 | if (iface != nameout && iface != unboundIface) 204 | { 205 | // SO_BINDTODEVICE 206 | if ((::setsockopt(udpSocket->native_handle(), SOL_SOCKET, 207 | SO_BINDTODEVICE, iface.c_str(), iface.size() + 1) == 208 | -1)) 209 | { 210 | lg2::error("Failed to bind to requested interface: {ERROR}", 211 | "ERROR", strerror(errno)); 212 | return EXIT_FAILURE; 213 | } 214 | lg2::info("Bind to interface: {INTERFACE}", "INTERFACE", iface); 215 | } 216 | // cannot be constexpr because it gets passed by address 217 | const int option_enabled = 1; 218 | // common socket stuff; set options to get packet info (DST addr) 219 | ::setsockopt(udpSocket->native_handle(), IPPROTO_IP, IP_PKTINFO, 220 | &option_enabled, sizeof(option_enabled)); 221 | ::setsockopt(udpSocket->native_handle(), IPPROTO_IPV6, IPV6_RECVPKTINFO, 222 | &option_enabled, sizeof(option_enabled)); 223 | 224 | // set the dbus name 225 | std::string busName = "xyz.openbmc_project.Ipmi.Channel." + channel; 226 | try 227 | { 228 | bus->request_name(busName.c_str()); 229 | } 230 | catch (const std::exception& e) 231 | { 232 | lg2::error("Failed to acquire D-Bus name: {NAME}: {ERROR}", "NAME", 233 | busName, "ERROR", e); 234 | return EXIT_FAILURE; 235 | } 236 | return 0; 237 | } 238 | 239 | int EventLoop::startEventLoop() 240 | { 241 | startRmcpReceive(); 242 | 243 | io->run(); 244 | 245 | return EXIT_SUCCESS; 246 | } 247 | 248 | void EventLoop::setupSignal() 249 | { 250 | static boost::asio::signal_set signals(*io, SIGINT, SIGTERM); 251 | signals.async_wait([this](const boost::system::error_code& /* error */, 252 | int /* signalNumber */) { 253 | if (udpSocket) 254 | { 255 | udpSocket->cancel(); 256 | udpSocket->close(); 257 | } 258 | io->stop(); 259 | }); 260 | } 261 | 262 | } // namespace eventloop 263 | -------------------------------------------------------------------------------- /message.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "config.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace message 11 | { 12 | 13 | enum class PayloadType : uint8_t 14 | { 15 | IPMI = 0x00, 16 | SOL = 0x01, 17 | OPEN_SESSION_REQUEST = 0x10, 18 | OPEN_SESSION_RESPONSE = 0x11, 19 | RAKP1 = 0x12, 20 | RAKP2 = 0x13, 21 | RAKP3 = 0x14, 22 | RAKP4 = 0x15, 23 | INVALID = 0xFF, 24 | }; 25 | 26 | // RMCP Classes of Message as per section 13.1.3. 27 | enum class ClassOfMsg : uint8_t 28 | { 29 | RESERVED = 0x05, 30 | ASF = 0x06, 31 | IPMI = 0x07, 32 | OEM = 0x08, 33 | }; 34 | 35 | #ifdef RMCP_PING 36 | // RMCP Message Type as per section 13.1.3. 37 | enum class RmcpMsgType : uint8_t 38 | { 39 | PING = 0x80, 40 | PONG = 0x40, 41 | }; 42 | #endif // RMCP_PING 43 | 44 | namespace LAN 45 | { 46 | 47 | constexpr uint8_t requesterBMCAddress = 0x20; 48 | constexpr uint8_t responderBMCAddress = 0x81; 49 | 50 | namespace header 51 | { 52 | 53 | /** 54 | * @struct IPMI LAN Message Request Header 55 | */ 56 | struct Request 57 | { 58 | uint8_t rsaddr; 59 | uint8_t netfn; 60 | uint8_t cs; 61 | uint8_t rqaddr; 62 | uint8_t rqseq; 63 | uint8_t cmd; 64 | } __attribute__((packed)); 65 | 66 | /** 67 | * @struct IPMI LAN Message Response Header 68 | */ 69 | struct Response 70 | { 71 | uint8_t rqaddr; 72 | uint8_t netfn; 73 | uint8_t cs; 74 | uint8_t rsaddr; 75 | uint8_t rqseq; 76 | uint8_t cmd; 77 | } __attribute__((packed)); 78 | 79 | } // namespace header 80 | 81 | namespace trailer 82 | { 83 | 84 | /** 85 | * @struct IPMI LAN Message Trailer 86 | */ 87 | struct Request 88 | { 89 | uint8_t checksum; 90 | } __attribute__((packed)); 91 | 92 | using Response = Request; 93 | 94 | } // namespace trailer 95 | 96 | } // namespace LAN 97 | 98 | /** 99 | * @brief Calculate 8 bit 2's complement checksum 100 | * 101 | * Initialize checksum to 0. For each byte, checksum = (checksum + byte) 102 | * modulo 256. Then checksum = - checksum. When the checksum and the 103 | * bytes are added together, modulo 256, the result should be 0. 104 | */ 105 | static inline uint8_t crc8bit(const uint8_t* ptr, const size_t len) 106 | { 107 | return (0x100 - std::accumulate(ptr, ptr + len, 0)); 108 | } 109 | 110 | /** 111 | * @struct Message 112 | * 113 | * IPMI message is data encapsulated in an IPMI Session packet. The IPMI 114 | * Session packets are encapsulated in RMCP packets, which are encapsulated in 115 | * UDP datagrams. Refer Section 13.5 of IPMI specification(IPMI Messages 116 | * Encapsulation Under RMCP). IPMI payload is a special class of data 117 | * encapsulated in an IPMI session packet. 118 | */ 119 | struct Message 120 | { 121 | static constexpr uint32_t MESSAGE_INVALID_SESSION_ID = 0xBADBADFF; 122 | 123 | Message() : 124 | payloadType(PayloadType::INVALID), 125 | rcSessionID(Message::MESSAGE_INVALID_SESSION_ID), 126 | bmcSessionID(Message::MESSAGE_INVALID_SESSION_ID), 127 | rmcpMsgClass(ClassOfMsg::RESERVED) 128 | {} 129 | 130 | /** 131 | * @brief Special behavior for copy constructor 132 | * 133 | * Based on incoming message state, the resulting message will have a 134 | * pre-baked state. This is used to simplify the flows for creating a 135 | * response message. For each pre-session state, the response message is 136 | * actually a different type of message. Once the session has been 137 | * established, the response type is the same as the request type. 138 | */ 139 | Message(const Message& other) : 140 | isPacketEncrypted(other.isPacketEncrypted), 141 | isPacketAuthenticated(other.isPacketAuthenticated), 142 | payloadType(other.payloadType), rcSessionID(other.rcSessionID), 143 | bmcSessionID(other.bmcSessionID), rmcpMsgClass(other.rmcpMsgClass) 144 | { 145 | // special behavior for rmcp+ session creation 146 | if (PayloadType::OPEN_SESSION_REQUEST == other.payloadType) 147 | { 148 | payloadType = PayloadType::OPEN_SESSION_RESPONSE; 149 | } 150 | else if (PayloadType::RAKP1 == other.payloadType) 151 | { 152 | payloadType = PayloadType::RAKP2; 153 | } 154 | else if (PayloadType::RAKP3 == other.payloadType) 155 | { 156 | payloadType = PayloadType::RAKP4; 157 | } 158 | } 159 | Message& operator=(const Message&) = default; 160 | Message(Message&&) = default; 161 | Message& operator=(Message&&) = default; 162 | ~Message() = default; 163 | 164 | /** 165 | * @brief Extract the command from the IPMI payload 166 | * 167 | * @return Command ID in the incoming message 168 | */ 169 | uint32_t getCommand() 170 | { 171 | uint32_t command = 0; 172 | 173 | command |= (static_cast(payloadType) << 16); 174 | if (payloadType == PayloadType::IPMI) 175 | { 176 | auto request = 177 | reinterpret_cast(payload.data()); 178 | command |= request->netfn << 8; 179 | command |= static_cast(request->cmd); 180 | } 181 | return command; 182 | } 183 | 184 | /** 185 | * @brief Create the response IPMI message 186 | * 187 | * The IPMI outgoing message is constructed out of payload and the 188 | * corresponding fields are populated. For the payload type IPMI, the 189 | * LAN message header and trailer are added. 190 | * 191 | * @param[in] output - Payload for outgoing message 192 | * 193 | * @return Outgoing message on success and nullptr on failure 194 | */ 195 | std::shared_ptr createResponse(std::vector& output) 196 | { 197 | // SOL packets don't reply; return NULL 198 | if (payloadType == PayloadType::SOL) 199 | { 200 | return nullptr; 201 | } 202 | auto outMessage = std::make_shared(*this); 203 | 204 | if (payloadType == PayloadType::IPMI) 205 | { 206 | outMessage->payloadType = PayloadType::IPMI; 207 | 208 | outMessage->payload.resize( 209 | sizeof(LAN::header::Response) + output.size() + 210 | sizeof(LAN::trailer::Response)); 211 | 212 | auto reqHeader = 213 | reinterpret_cast(payload.data()); 214 | auto respHeader = reinterpret_cast( 215 | outMessage->payload.data()); 216 | 217 | // Add IPMI LAN Message Response Header 218 | respHeader->rqaddr = reqHeader->rqaddr; 219 | respHeader->netfn = reqHeader->netfn | 0x04; 220 | respHeader->cs = crc8bit(&(respHeader->rqaddr), 2); 221 | respHeader->rsaddr = reqHeader->rsaddr; 222 | respHeader->rqseq = reqHeader->rqseq; 223 | respHeader->cmd = reqHeader->cmd; 224 | 225 | auto assembledSize = sizeof(LAN::header::Response); 226 | 227 | // Copy the output by the execution of the command 228 | std::copy(output.begin(), output.end(), 229 | outMessage->payload.begin() + assembledSize); 230 | assembledSize += output.size(); 231 | 232 | // Add the IPMI LAN Message Trailer 233 | auto trailer = reinterpret_cast( 234 | outMessage->payload.data() + assembledSize); 235 | trailer->checksum = crc8bit(&respHeader->rsaddr, assembledSize - 3); 236 | } 237 | else 238 | { 239 | outMessage->payload = output; 240 | } 241 | return outMessage; 242 | } 243 | 244 | bool isPacketEncrypted; // Message's Encryption Status 245 | bool isPacketAuthenticated; // Message's Authentication Status 246 | PayloadType payloadType; // Type of message payload (IPMI,SOL ..etc) 247 | uint32_t rcSessionID; // Remote Client's Session ID 248 | uint32_t bmcSessionID; // BMC's session ID 249 | uint32_t sessionSeqNum; // Session Sequence Number 250 | ClassOfMsg rmcpMsgClass; // Class of Message 251 | #ifdef RMCP_PING 252 | uint8_t asfMsgTag; // ASF Message Tag 253 | #endif // RMCP_PING 254 | 255 | /** @brief Message payload 256 | * 257 | * “Payloads” are a capability specified for RMCP+ that enable an IPMI 258 | * session to carry types of traffic that are in addition to IPMI Messages. 259 | * Payloads can be ‘standard’ or ‘OEM’.Standard payload types include IPMI 260 | * Messages, messages for session setup under RMCP+, and the payload for 261 | * the “Serial Over LAN” capability introduced in IPMI v2.0. 262 | */ 263 | std::vector payload; 264 | }; 265 | 266 | } // namespace message 267 | -------------------------------------------------------------------------------- /socket_channel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace udpsocket 17 | { 18 | static constexpr uint8_t v4v6Index = 12; 19 | 20 | /** @class Channel 21 | * 22 | * @brief Provides encapsulation for UDP socket operations like Read, Peek, 23 | * Write, Remote peer's IP Address and Port. 24 | */ 25 | class Channel 26 | { 27 | public: 28 | Channel() = delete; 29 | ~Channel() = default; 30 | Channel(const Channel& right) = delete; 31 | Channel& operator=(const Channel& right) = delete; 32 | Channel(Channel&&) = delete; 33 | Channel& operator=(Channel&&) = delete; 34 | 35 | /** 36 | * @brief Constructor 37 | * 38 | * Initialize the IPMI socket object with the socket descriptor 39 | * 40 | * @param [in] pointer to a boost::asio udp socket object 41 | * 42 | * @return None 43 | */ 44 | explicit Channel(std::shared_ptr socket) : 45 | socket(socket) 46 | {} 47 | /** 48 | * @brief Check if ip address is ipv4 mapped ipv6 49 | * 50 | * @param v6Addr : in6_addr obj 51 | * 52 | * @return true if ipv4 mapped ipv6 else return false 53 | */ 54 | bool isIpv4InIpv6(const struct in6_addr& v6Addr) const 55 | { 56 | constexpr uint8_t prefix[v4v6Index] = {0, 0, 0, 0, 0, 0, 57 | 0, 0, 0, 0, 0xff, 0xff}; 58 | return 0 == std::memcmp(&v6Addr.s6_addr[0], &prefix[0], sizeof(prefix)); 59 | } 60 | /** 61 | * @brief Fetch the IP address of the remote peer 62 | * 63 | * @param remoteIpv4Addr : ipv4 address is assigned to it. 64 | * 65 | * Returns the IP address of the remote peer which is connected to this 66 | * socket 67 | * 68 | * @return IP address of the remote peer 69 | */ 70 | std::string getRemoteAddress(uint32_t& remoteIpv4Addr) const 71 | { 72 | const char* retval = nullptr; 73 | if (sockAddrSize == sizeof(sockaddr_in)) 74 | { 75 | char ipv4addr[INET_ADDRSTRLEN]; 76 | const sockaddr_in* sa = 77 | reinterpret_cast(&remoteSockAddr); 78 | remoteIpv4Addr = sa->sin_addr.s_addr; 79 | retval = 80 | inet_ntop(AF_INET, &(sa->sin_addr), ipv4addr, sizeof(ipv4addr)); 81 | } 82 | else if (sockAddrSize == sizeof(sockaddr_in6)) 83 | { 84 | char ipv6addr[INET6_ADDRSTRLEN]; 85 | const sockaddr_in6* sa = 86 | reinterpret_cast(&remoteSockAddr); 87 | 88 | if (isIpv4InIpv6(sa->sin6_addr)) 89 | { 90 | std::copy_n(&sa->sin6_addr.s6_addr[v4v6Index], 91 | sizeof(remoteIpv4Addr), 92 | reinterpret_cast(&remoteIpv4Addr)); 93 | } 94 | retval = inet_ntop(AF_INET6, &(sa->sin6_addr), ipv6addr, 95 | sizeof(ipv6addr)); 96 | } 97 | 98 | if (retval) 99 | { 100 | return retval; 101 | } 102 | lg2::error("Error in inet_ntop: {ERROR}", "ERROR", strerror(errno)); 103 | return std::string(); 104 | } 105 | 106 | /** 107 | * @brief Fetch the port number of the remote peer 108 | * 109 | * Returns the port number of the remote peer 110 | * 111 | * @return Port number 112 | * 113 | */ 114 | uint16_t getPort() const 115 | { 116 | if (sockAddrSize == sizeof(sockaddr_in)) 117 | { 118 | return ntohs(reinterpret_cast(&remoteSockAddr) 119 | ->sin_port); 120 | } 121 | if (sockAddrSize == sizeof(sockaddr_in6)) 122 | { 123 | return ntohs(reinterpret_cast(&remoteSockAddr) 124 | ->sin6_port); 125 | } 126 | return 0; 127 | } 128 | 129 | /** 130 | * @brief Read the incoming packet 131 | * 132 | * Reads the data available on the socket 133 | * 134 | * @return A tuple with return code and vector with the buffer 135 | * In case of success, the vector is populated with the data 136 | * available on the socket and return code is 0. 137 | * In case of error, the return code is < 0 and vector is set 138 | * to size 0. 139 | */ 140 | std::tuple> read() 141 | { 142 | // cannot use the standard asio reading mechanism because it does not 143 | // provide a mechanism to reach down into the depths and use a msghdr 144 | std::vector packet(socket->available()); 145 | iovec iov = {packet.data(), packet.size()}; 146 | char msgCtrl[1024]; 147 | msghdr msg = {&remoteSockAddr, sizeof(remoteSockAddr), &iov, 1, 148 | msgCtrl, sizeof(msgCtrl), 0}; 149 | 150 | ssize_t bytesReceived = recvmsg(socket->native_handle(), &msg, 0); 151 | // Read of the packet failed 152 | if (bytesReceived < 0) 153 | { 154 | // something bad happened; bail 155 | lg2::error("Error in recvmsg: {ERROR}", "ERROR", 156 | strerror(-bytesReceived)); 157 | return std::make_tuple(-errno, std::vector()); 158 | } 159 | // save the size of either ipv4 or i4v6 sockaddr 160 | sockAddrSize = msg.msg_namelen; 161 | 162 | // extract the destination address from the message 163 | cmsghdr* cmsg; 164 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0; 165 | cmsg = CMSG_NXTHDR(&msg, cmsg)) 166 | { 167 | if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) 168 | { 169 | // save local address from the pktinfo4 170 | pktinfo4 = *reinterpret_cast(CMSG_DATA(cmsg)); 171 | } 172 | if (cmsg->cmsg_level == IPPROTO_IPV6 && 173 | cmsg->cmsg_type == IPV6_PKTINFO) 174 | { 175 | // save local address from the pktinfo6 176 | pktinfo6 = *reinterpret_cast(CMSG_DATA(cmsg)); 177 | } 178 | } 179 | return std::make_tuple(0, packet); 180 | } 181 | 182 | /** 183 | * @brief Write the outgoing packet 184 | * 185 | * Writes the data in the vector to the socket 186 | * 187 | * @param [in] inBuffer 188 | * The vector would be the buffer of data to write to the socket. 189 | * 190 | * @return In case of success the return code is the number of bytes 191 | * written and return code is < 0 in case of failure. 192 | */ 193 | int write(const std::vector& inBuffer) 194 | { 195 | // in order to make sure packets go back out from the same 196 | // IP address they came in on, sendmsg must be used instead 197 | // of the boost::asio::ip::send or sendto 198 | iovec iov = {const_cast(inBuffer.data()), inBuffer.size()}; 199 | char msgCtrl[1024]; 200 | msghdr msg = {&remoteSockAddr, sockAddrSize, &iov, 1, 201 | msgCtrl, sizeof(msgCtrl), 0}; 202 | int cmsg_space = 0; 203 | cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); 204 | if (pktinfo6) 205 | { 206 | cmsg->cmsg_level = IPPROTO_IPV6; 207 | cmsg->cmsg_type = IPV6_PKTINFO; 208 | cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); 209 | *reinterpret_cast(CMSG_DATA(cmsg)) = *pktinfo6; 210 | cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo)); 211 | } 212 | else if (pktinfo4) 213 | { 214 | cmsg->cmsg_level = IPPROTO_IP; 215 | cmsg->cmsg_type = IP_PKTINFO; 216 | cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); 217 | *reinterpret_cast(CMSG_DATA(cmsg)) = *pktinfo4; 218 | cmsg_space += CMSG_SPACE(sizeof(in_pktinfo)); 219 | } 220 | msg.msg_controllen = cmsg_space; 221 | int ret = sendmsg(socket->native_handle(), &msg, 0); 222 | if (ret < 0) 223 | { 224 | lg2::error("Error in sendmsg: {ERROR}", "ERROR", strerror(-ret)); 225 | } 226 | return ret; 227 | } 228 | 229 | /** 230 | * @brief Returns file descriptor for the socket 231 | */ 232 | auto getHandle(void) const 233 | { 234 | return socket->native_handle(); 235 | } 236 | 237 | private: 238 | std::shared_ptr socket; 239 | sockaddr_storage remoteSockAddr; 240 | socklen_t sockAddrSize; 241 | std::optional pktinfo4; 242 | std::optional pktinfo6; 243 | }; 244 | 245 | } // namespace udpsocket 246 | -------------------------------------------------------------------------------- /message_parsers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message.hpp" 4 | #include "session.hpp" 5 | 6 | #include 7 | 8 | namespace message 9 | { 10 | 11 | namespace parser 12 | { 13 | 14 | constexpr size_t RMCP_VERSION = 6; 15 | 16 | // RMCP Messages with class=IPMI should be sent with an RMCP Sequence 17 | // Number of FFh to indicate that an RMCP ACK message should not be 18 | // generated by the message receiver. 19 | constexpr size_t RMCP_SEQ = 0xFF; 20 | 21 | // RMCP Message Class 7h is for IPMI 22 | constexpr size_t RMCP_MESSAGE_CLASS_IPMI = 7; 23 | 24 | // RMCP Session Header Size 25 | constexpr size_t RMCP_SESSION_HEADER_SIZE = 4; 26 | 27 | // RMCP/ASF Pong Message ASF Header Data Length 28 | // as per IPMI spec 13.2.4 29 | constexpr size_t RMCP_ASF_PONG_DATA_LEN = 16; 30 | 31 | // ASF IANA 32 | constexpr uint32_t ASF_IANA = 4542; 33 | 34 | // ASF Supported Entities 35 | constexpr uint32_t ASF_SUPP_ENT = 0x81; 36 | 37 | // ASF Supported Entities 38 | constexpr uint32_t ASF_SUPP_INT = 0x00; 39 | 40 | // Maximum payload size 41 | constexpr size_t MAX_PAYLOAD_SIZE = 255; 42 | 43 | enum class SessionHeader 44 | { 45 | IPMI15 = 0x00, 46 | IPMI20 = 0x06, 47 | INVALID = 0xFF, 48 | }; 49 | 50 | // RMCP Header 51 | struct RmcpHeader_t 52 | { 53 | // RMCP Header 54 | uint8_t version; 55 | uint8_t reserved; 56 | uint8_t rmcpSeqNum; 57 | uint8_t classOfMsg; 58 | } __attribute__((packed)); 59 | 60 | struct BasicHeader_t 61 | { 62 | // RMCP Header 63 | struct RmcpHeader_t rmcp; 64 | 65 | // IPMI partial session header 66 | union 67 | { 68 | uint8_t reserved1:4; 69 | uint8_t authType:4; 70 | uint8_t formatType; 71 | } format; 72 | } __attribute__((packed)); 73 | 74 | /** 75 | * @brief Unflatten an incoming packet and prepare the IPMI message 76 | * 77 | * @param[in] inPacket - Incoming IPMI packet 78 | * 79 | * @return A tuple with IPMI message and the session header type to sent the 80 | * response packet. In case of success incoming message and session 81 | * header type. In case of failure nullptr and session header type 82 | * would be invalid. 83 | */ 84 | std::tuple, SessionHeader> unflatten( 85 | std::vector& inPacket); 86 | 87 | /** 88 | * @brief Flatten an IPMI message and generate the IPMI packet with the 89 | * session header 90 | * 91 | * @param[in] outMessage - IPMI message to be flattened 92 | * @param[in] authType - Session header type to be added to the IPMI 93 | * packet 94 | * 95 | * @return IPMI packet on success 96 | */ 97 | std::vector flatten(const std::shared_ptr& outMessage, 98 | SessionHeader authType, 99 | const std::shared_ptr& session); 100 | 101 | } // namespace parser 102 | 103 | namespace ipmi15parser 104 | { 105 | 106 | struct SessionHeader_t 107 | { 108 | struct parser::BasicHeader_t base; 109 | uint32_t sessSeqNum; 110 | uint32_t sessId; 111 | // 112 | uint8_t payloadLength; 113 | } __attribute__((packed)); 114 | 115 | struct SessionTrailer_t 116 | { 117 | uint8_t legacyPad; 118 | } __attribute__((packed)); 119 | 120 | /** 121 | * @brief Unflatten an incoming packet and prepare the IPMI message 122 | * 123 | * @param[in] inPacket - Incoming IPMI packet 124 | * 125 | * @return IPMI message in the packet on success 126 | */ 127 | std::shared_ptr unflatten(std::vector& inPacket); 128 | 129 | /** 130 | * @brief Flatten an IPMI message and generate the IPMI packet with the 131 | * session header 132 | * 133 | * @param[in] outMessage - IPMI message to be flattened 134 | * 135 | * @return IPMI packet on success 136 | */ 137 | std::vector flatten(const std::shared_ptr& outMessage, 138 | const std::shared_ptr& session); 139 | 140 | } // namespace ipmi15parser 141 | 142 | namespace ipmi20parser 143 | { 144 | 145 | constexpr size_t MAX_INTEGRITY_DATA_LENGTH = 12; 146 | constexpr size_t PAYLOAD_ENCRYPT_MASK = 0x80; 147 | constexpr size_t PAYLOAD_AUTH_MASK = 0x40; 148 | 149 | struct SessionHeader_t 150 | { 151 | struct parser::BasicHeader_t base; 152 | 153 | uint8_t payloadType; 154 | 155 | uint32_t sessId; 156 | uint32_t sessSeqNum; 157 | uint16_t payloadLength; 158 | } __attribute__((packed)); 159 | 160 | struct SessionTrailer_t 161 | { 162 | // Integrity Pad 163 | uint8_t padLength; 164 | uint8_t nextHeader; 165 | } __attribute__((packed)); 166 | 167 | /** 168 | * @brief Unflatten an incoming packet and prepare the IPMI message 169 | * 170 | * @param[in] inPacket - Incoming IPMI packet 171 | * 172 | * @return IPMI message in the packet on success 173 | */ 174 | std::shared_ptr unflatten(std::vector& inPacket); 175 | 176 | /** 177 | * @brief Flatten an IPMI message and generate the IPMI packet with the 178 | * session header 179 | * 180 | * @param[in] outMessage - IPMI message to be flattened 181 | * @param[in] session - session handle 182 | * 183 | * @return IPMI packet on success 184 | */ 185 | std::vector flatten(const std::shared_ptr& outMessage, 186 | const std::shared_ptr& session); 187 | 188 | namespace internal 189 | { 190 | 191 | /** 192 | * @brief Add sequence number to the message 193 | * 194 | * @param[in] packet - outgoing packet to which to add sequence number 195 | * @param[in] session - session handle 196 | * 197 | */ 198 | void addSequenceNumber(std::vector& packet, 199 | const std::shared_ptr& session); 200 | 201 | /** 202 | * @brief Verify the integrity data of the incoming IPMI packet 203 | * 204 | * @param[in] packet - Incoming IPMI packet 205 | * @param[in] message - IPMI Message populated from the incoming packet 206 | * @param[in] payloadLen - Length of the IPMI payload 207 | * @param[in] session - session handle 208 | * 209 | */ 210 | bool verifyPacketIntegrity(const std::vector& packet, 211 | const std::shared_ptr& message, 212 | size_t payloadLen, 213 | const std::shared_ptr& session); 214 | 215 | /** 216 | * @brief Add Integrity data to the outgoing IPMI packet 217 | * 218 | * @param[in] packet - Outgoing IPMI packet 219 | * @param[in] message - IPMI Message populated for the outgoing packet 220 | * @param[in] payloadLen - Length of the IPMI payload 221 | */ 222 | void addIntegrityData(std::vector& packet, 223 | const std::shared_ptr& message, 224 | size_t payloadLen, 225 | const std::shared_ptr& session); 226 | 227 | /** 228 | * @brief Decrypt the encrypted payload in the incoming IPMI packet 229 | * 230 | * @param[in] packet - Incoming IPMI packet 231 | * @param[in] message - IPMI Message populated from the incoming packet 232 | * @param[in] payloadLen - Length of encrypted IPMI payload 233 | * @param[in] session - session handle 234 | * 235 | * @return on successful completion, return the plain text payload 236 | */ 237 | std::vector decryptPayload( 238 | const std::vector& packet, const std::shared_ptr& message, 239 | size_t payloadLen, const std::shared_ptr& session); 240 | 241 | /** 242 | * @brief Encrypt the plain text payload for the outgoing IPMI packet 243 | * 244 | * @param[in] message - IPMI Message populated for the outgoing packet 245 | * @param[in] session - session handle 246 | * 247 | * @return on successful completion, return the encrypted payload 248 | */ 249 | std::vector encryptPayload( 250 | const std::shared_ptr& message, 251 | const std::shared_ptr& session); 252 | 253 | } // namespace internal 254 | 255 | } // namespace ipmi20parser 256 | 257 | #ifdef RMCP_PING 258 | namespace asfparser 259 | { 260 | 261 | // ASF message fields for RMCP Ping message 262 | struct AsfMessagePing_t 263 | { 264 | struct parser::RmcpHeader_t rmcp; 265 | 266 | uint32_t iana; 267 | uint8_t msgType; 268 | uint8_t msgTag; 269 | uint8_t reserved; 270 | uint8_t dataLen; 271 | } __attribute__((packed)); 272 | 273 | // ASF message fields for RMCP Pong message 274 | struct AsfMessagePong_t 275 | { 276 | struct AsfMessagePing_t ping; 277 | 278 | uint32_t iana; 279 | uint32_t oemDefined; 280 | uint8_t suppEntities; 281 | uint8_t suppInteract; 282 | uint32_t reserved1; 283 | uint16_t reserved2; 284 | } __attribute__((packed)); 285 | 286 | /** 287 | * @brief Unflatten an incoming packet and prepare the ASF message 288 | * 289 | * @param[in] inPacket - Incoming ASF packet 290 | * 291 | * @return ASF message in the packet on success 292 | */ 293 | std::shared_ptr unflatten(std::vector& inPacket); 294 | 295 | /** 296 | * @brief Generate the ASF packet with the RMCP header 297 | * 298 | * @param[in] asfMsgTag - ASF Message Tag from Ping request 299 | * 300 | * @return ASF packet on success 301 | */ 302 | std::vector flatten(uint8_t asfMsgTag); 303 | 304 | } // namespace asfparser 305 | #endif // RMCP_PING 306 | 307 | } // namespace message 308 | -------------------------------------------------------------------------------- /command/rakp34.cpp: -------------------------------------------------------------------------------- 1 | #include "rakp34.hpp" 2 | 3 | #include "comm_module.hpp" 4 | #include "endian.hpp" 5 | #include "guid.hpp" 6 | #include "rmcp.hpp" 7 | #include "sessions_manager.hpp" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace command 15 | { 16 | 17 | void applyIntegrityAlgo(const uint32_t bmcSessionID) 18 | { 19 | auto session = session::Manager::get().getSession(bmcSessionID); 20 | 21 | auto authAlgo = session->getAuthAlgo(); 22 | 23 | switch (authAlgo->intAlgo) 24 | { 25 | case cipher::integrity::Algorithms::HMAC_SHA1_96: 26 | { 27 | session->setIntegrityAlgo( 28 | std::make_unique( 29 | authAlgo->sessionIntegrityKey)); 30 | break; 31 | } 32 | case cipher::integrity::Algorithms::HMAC_SHA256_128: 33 | { 34 | session->setIntegrityAlgo( 35 | std::make_unique( 36 | authAlgo->sessionIntegrityKey)); 37 | break; 38 | } 39 | default: 40 | break; 41 | } 42 | } 43 | 44 | void applyCryptAlgo(const uint32_t bmcSessionID) 45 | { 46 | auto session = session::Manager::get().getSession(bmcSessionID); 47 | 48 | auto authAlgo = session->getAuthAlgo(); 49 | 50 | switch (authAlgo->cryptAlgo) 51 | { 52 | case cipher::crypt::Algorithms::AES_CBC_128: 53 | { 54 | auto intAlgo = session->getIntegrityAlgo(); 55 | auto k2 = intAlgo->generateKn(authAlgo->sessionIntegrityKey, 56 | rmcp::const_2); 57 | session->setCryptAlgo( 58 | std::make_unique(k2)); 59 | break; 60 | } 61 | default: 62 | break; 63 | } 64 | } 65 | 66 | std::vector RAKP34(const std::vector& inPayload, 67 | std::shared_ptr& /* handler */) 68 | { 69 | std::vector outPayload(sizeof(RAKP4response)); 70 | auto request = reinterpret_cast(inPayload.data()); 71 | auto response = reinterpret_cast(outPayload.data()); 72 | 73 | // Check if the RAKP3 Payload Length is as expected 74 | if (inPayload.size() < sizeof(RAKP3request)) 75 | { 76 | lg2::info("RAKP34: Invalid RAKP3 request"); 77 | response->rmcpStatusCode = 78 | static_cast(RAKP_ReturnCode::INVALID_INTEGRITY_VALUE); 79 | return outPayload; 80 | } 81 | 82 | // Session ID zero is reserved for Session Setup 83 | if (endian::from_ipmi(request->managedSystemSessionID) == 84 | session::sessionZero) 85 | { 86 | lg2::info("RAKP34: BMC invalid Session ID"); 87 | response->rmcpStatusCode = 88 | static_cast(RAKP_ReturnCode::INVALID_SESSION_ID); 89 | return outPayload; 90 | } 91 | 92 | std::shared_ptr session; 93 | try 94 | { 95 | session = 96 | session::Manager::get().getSession(request->managedSystemSessionID); 97 | } 98 | catch (const std::exception& e) 99 | { 100 | lg2::error("RAKP12 : session not found: {ERROR}", "ERROR", e); 101 | response->rmcpStatusCode = 102 | static_cast(RAKP_ReturnCode::INVALID_SESSION_ID); 103 | return outPayload; 104 | } 105 | 106 | session->updateLastTransactionTime(); 107 | 108 | auto authAlgo = session->getAuthAlgo(); 109 | /* 110 | * Key Authentication Code - RAKP 3 111 | * 112 | * 1) Managed System Random Number - 16 bytes 113 | * 2) Remote Console Session ID - 4 bytes 114 | * 3) Session Privilege Level - 1 byte 115 | * 4) User Name Length Byte - 1 byte (0 for 'null' username) 116 | * 5) User Name - variable (absent for 'null' username) 117 | */ 118 | 119 | // Remote Console Session ID 120 | auto rcSessionID = endian::to_ipmi(session->getRCSessionID()); 121 | 122 | // Session Privilege Level 123 | auto sessPrivLevel = static_cast(session->reqMaxPrivLevel); 124 | 125 | // User Name Length Byte 126 | auto userLength = static_cast(session->userName.size()); 127 | 128 | std::vector input; 129 | input.resize(cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN + 130 | sizeof(rcSessionID) + sizeof(sessPrivLevel) + 131 | sizeof(userLength) + userLength); 132 | 133 | auto iter = input.begin(); 134 | 135 | // Managed System Random Number 136 | std::copy(authAlgo->bmcRandomNum.begin(), authAlgo->bmcRandomNum.end(), 137 | iter); 138 | std::advance(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN); 139 | 140 | // Remote Console Session ID 141 | std::copy_n(reinterpret_cast(&rcSessionID), sizeof(rcSessionID), 142 | iter); 143 | std::advance(iter, sizeof(rcSessionID)); 144 | 145 | // Session Privilege Level 146 | std::copy_n(reinterpret_cast(&sessPrivLevel), 147 | sizeof(sessPrivLevel), iter); 148 | std::advance(iter, sizeof(sessPrivLevel)); 149 | 150 | // User Name Length Byte 151 | std::copy_n(&userLength, sizeof(userLength), iter); 152 | std::advance(iter, sizeof(userLength)); 153 | 154 | std::copy_n(session->userName.data(), userLength, iter); 155 | 156 | // Generate Key Exchange Authentication Code - RAKP2 157 | auto output = authAlgo->generateHMAC(input); 158 | 159 | if (inPayload.size() != (sizeof(RAKP3request) + output.size()) || 160 | std::memcmp(output.data(), request + 1, output.size())) 161 | { 162 | lg2::info("Mismatch in HMAC sent by remote console"); 163 | 164 | response->messageTag = request->messageTag; 165 | response->rmcpStatusCode = 166 | static_cast(RAKP_ReturnCode::INVALID_INTEGRITY_VALUE); 167 | response->reserved = 0; 168 | response->remoteConsoleSessionID = rcSessionID; 169 | 170 | // close the session 171 | session::Manager::get().stopSession(session->getBMCSessionID()); 172 | 173 | return outPayload; 174 | } 175 | 176 | /* 177 | * Session Integrity Key 178 | * 179 | * 1) Remote Console Random Number - 16 bytes 180 | * 2) Managed System Random Number - 16 bytes 181 | * 3) Session Privilege Level - 1 byte 182 | * 4) User Name Length Byte - 1 byte (0 for 'null' username) 183 | * 5) User Name - variable (absent for 'null' username) 184 | */ 185 | 186 | input.clear(); 187 | 188 | input.resize(cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN + 189 | cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN + 190 | sizeof(sessPrivLevel) + sizeof(userLength) + userLength); 191 | iter = input.begin(); 192 | 193 | // Remote Console Random Number 194 | std::copy(authAlgo->rcRandomNum.begin(), authAlgo->rcRandomNum.end(), iter); 195 | std::advance(iter, cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN); 196 | 197 | // Managed Console Random Number 198 | std::copy(authAlgo->bmcRandomNum.begin(), authAlgo->bmcRandomNum.end(), 199 | iter); 200 | std::advance(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN); 201 | 202 | // Session Privilege Level 203 | std::copy_n(reinterpret_cast(&sessPrivLevel), 204 | sizeof(sessPrivLevel), iter); 205 | std::advance(iter, sizeof(sessPrivLevel)); 206 | 207 | // User Name Length Byte 208 | std::copy_n(&userLength, sizeof(userLength), iter); 209 | std::advance(iter, sizeof(userLength)); 210 | 211 | std::copy_n(session->userName.data(), userLength, iter); 212 | 213 | // Generate Session Integrity Key 214 | auto sikOutput = authAlgo->generateHMAC(input); 215 | 216 | // Update the SIK in the Authentication Algo Interface 217 | authAlgo->sessionIntegrityKey.insert(authAlgo->sessionIntegrityKey.begin(), 218 | sikOutput.begin(), sikOutput.end()); 219 | 220 | /* 221 | * Integrity Check Value 222 | * 223 | * 1) Remote Console Random Number - 16 bytes 224 | * 2) Managed System Session ID - 4 bytes 225 | * 3) Managed System GUID - 16 bytes 226 | */ 227 | 228 | // Get Managed System Session ID 229 | auto bmcSessionID = endian::to_ipmi(session->getBMCSessionID()); 230 | 231 | input.clear(); 232 | 233 | input.resize(cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN + 234 | sizeof(bmcSessionID) + BMC_GUID_LEN); 235 | iter = input.begin(); 236 | 237 | // Remote Console Random Number 238 | std::copy(authAlgo->rcRandomNum.begin(), authAlgo->rcRandomNum.end(), iter); 239 | std::advance(iter, cipher::rakp_auth::REMOTE_CONSOLE_RANDOM_NUMBER_LEN); 240 | 241 | // Managed System Session ID 242 | std::copy_n(reinterpret_cast(&bmcSessionID), sizeof(bmcSessionID), 243 | iter); 244 | std::advance(iter, sizeof(bmcSessionID)); 245 | 246 | // Managed System GUID 247 | const Guid& guid = command::getSystemGUID(); 248 | std::copy_n(guid.data(), guid.size(), iter); 249 | 250 | // Integrity Check Value 251 | auto icv = authAlgo->generateICV(input); 252 | 253 | outPayload.resize(sizeof(RAKP4response)); 254 | 255 | response->messageTag = request->messageTag; 256 | response->rmcpStatusCode = static_cast(RAKP_ReturnCode::NO_ERROR); 257 | response->reserved = 0; 258 | response->remoteConsoleSessionID = rcSessionID; 259 | 260 | // Insert the HMAC output into the payload 261 | outPayload.insert(outPayload.end(), icv.begin(), icv.end()); 262 | 263 | // Set the Integrity Algorithm 264 | applyIntegrityAlgo(session->getBMCSessionID()); 265 | 266 | // Set the Confidentiality Algorithm 267 | applyCryptAlgo(session->getBMCSessionID()); 268 | 269 | session->state(static_cast(session::State::active)); 270 | return outPayload; 271 | } 272 | 273 | } // namespace command 274 | -------------------------------------------------------------------------------- /session.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "auth_algo.hpp" 4 | #include "crypt_algo.hpp" 5 | #include "endian.hpp" 6 | #include "integrity_algo.hpp" 7 | #include "prng.hpp" 8 | #include "socket_channel.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace session 27 | { 28 | 29 | using namespace std::chrono_literals; 30 | using SessionID = uint32_t; 31 | 32 | enum class Privilege : uint8_t 33 | { 34 | HIGHEST_MATCHING, 35 | CALLBACK, 36 | USER, 37 | OPERATOR, 38 | ADMIN, 39 | OEM, 40 | }; 41 | 42 | // Mask to get only the privilege from requested maximum privlege (RAKP message 43 | // 1) 44 | constexpr uint8_t reqMaxPrivMask = 0xF; 45 | 46 | /** 47 | * @struct SequenceNumbers Session Sequence Numbers 48 | * 49 | * IPMI v2.0 RMCP+ Session Sequence Numbers are used for rejecting packets that 50 | * may have been duplicated by the network or intentionally replayed. There are 51 | * two sets of Session SequenceNumbers for a given session.One set of inbound 52 | * and outbound sequence numbers is used for authenticated (signed) packets, 53 | * and the other set is used for unauthenticated packets. 54 | * 55 | * The individual Session Sequence Numbers is are initialized to zero whenever 56 | * a session is created and incremented by one at the start of outbound 57 | * processing for a given packet (i.e. the first transmitted packet has a ‘1’ 58 | * as the sequence number, not 0). Session Sequence numbers are incremented for 59 | * every packet that is transmitted by a given sender, regardless of whether 60 | * the payload for the packet is a ‘retry’ or not. 61 | */ 62 | struct SequenceNumbers 63 | { 64 | auto get(bool inbound = true) const 65 | { 66 | return inbound ? in : out; 67 | } 68 | 69 | void set(uint32_t seqNumber, bool inbound = true) 70 | { 71 | inbound ? (in = seqNumber) : (out = seqNumber); 72 | } 73 | 74 | auto increment() 75 | { 76 | return ++out; 77 | } 78 | 79 | private: 80 | uint32_t in = 0; 81 | uint32_t out = 0; 82 | }; 83 | /** 84 | * @class Session 85 | * 86 | * Encapsulates the data related to an IPMI Session 87 | * 88 | * Authenticated IPMI communication to the BMC is accomplished by establishing 89 | * a session. Once established, a session is identified by a Session ID. The 90 | * Session ID may be thought of as a handle that identifies a connection between 91 | * a given remote user and the BMC. The specification supports having multiple 92 | * active sessions established with the BMC. It is recommended that a BMC 93 | * implementation support at least four simultaneous sessions 94 | */ 95 | 96 | using SessionIface = sdbusplus::server::object_t< 97 | sdbusplus::xyz::openbmc_project::Ipmi::server::SessionInfo>; 98 | 99 | class Session : public SessionIface 100 | { 101 | public: 102 | Session() = delete; 103 | ~Session() = default; 104 | Session(const Session&) = delete; 105 | Session& operator=(const Session&) = delete; 106 | Session(Session&&) = delete; 107 | Session& operator=(Session&&) = delete; 108 | 109 | /** 110 | * @brief Session Constructor 111 | * 112 | * This is issued by the Session Manager when a session is started for 113 | * the Open SessionRequest command 114 | * 115 | * @param[in] inRemoteConsoleSessID - Remote Console Session ID 116 | * @param[in] priv - Privilege Level requested in the Command 117 | */ 118 | Session(sdbusplus::bus_t& bus, const char* path, 119 | SessionID inRemoteConsoleSessID, SessionID BMCSessionID, 120 | char priv) : SessionIface(bus, path) 121 | { 122 | reqMaxPrivLevel = static_cast(priv); 123 | bmcSessionID = BMCSessionID; 124 | remoteConsoleSessionID = inRemoteConsoleSessID; 125 | } 126 | 127 | auto getBMCSessionID() const 128 | { 129 | return bmcSessionID; 130 | } 131 | 132 | auto getRCSessionID() const 133 | { 134 | return remoteConsoleSessionID; 135 | } 136 | 137 | auto getAuthAlgo() const 138 | { 139 | if (authAlgoInterface) 140 | { 141 | return authAlgoInterface.get(); 142 | } 143 | else 144 | { 145 | throw std::runtime_error("Authentication Algorithm Empty"); 146 | } 147 | } 148 | 149 | void setAuthAlgo(std::unique_ptr&& inAuthAlgo) 150 | { 151 | authAlgoInterface = std::move(inAuthAlgo); 152 | } 153 | 154 | /** 155 | * @brief Get Session's Integrity Algorithm 156 | * 157 | * @return pointer to the integrity algorithm 158 | */ 159 | auto getIntegrityAlgo() const 160 | { 161 | if (integrityAlgoInterface) 162 | { 163 | return integrityAlgoInterface.get(); 164 | } 165 | else 166 | { 167 | throw std::runtime_error("Integrity Algorithm Empty"); 168 | } 169 | } 170 | 171 | /** 172 | * @brief Set Session's Integrity Algorithm 173 | * 174 | * @param[in] integrityAlgo - unique pointer to integrity algorithm 175 | * instance 176 | */ 177 | void setIntegrityAlgo( 178 | std::unique_ptr&& integrityAlgo) 179 | { 180 | integrityAlgoInterface = std::move(integrityAlgo); 181 | } 182 | 183 | /** @brief Check if integrity algorithm is enabled for this session. 184 | * 185 | * @return true if integrity algorithm is enabled else false. 186 | */ 187 | auto isIntegrityAlgoEnabled() 188 | { 189 | return integrityAlgoInterface ? true : false; 190 | } 191 | 192 | /** 193 | * @brief Get Session's Confidentiality Algorithm 194 | * 195 | * @return pointer to the confidentiality algorithm 196 | */ 197 | auto getCryptAlgo() const 198 | { 199 | if (cryptAlgoInterface) 200 | { 201 | return cryptAlgoInterface.get(); 202 | } 203 | else 204 | { 205 | throw std::runtime_error("Confidentiality Algorithm Empty"); 206 | } 207 | } 208 | 209 | /** 210 | * @brief Set Session's Confidentiality Algorithm 211 | * 212 | * @param[in] confAlgo - unique pointer to confidentiality algorithm 213 | * instance 214 | */ 215 | void setCryptAlgo(std::unique_ptr&& cryptAlgo) 216 | { 217 | cryptAlgoInterface = std::move(cryptAlgo); 218 | } 219 | 220 | /** @brief Check if confidentiality algorithm is enabled for this 221 | * session. 222 | * 223 | * @return true if confidentiality algorithm is enabled else false. 224 | */ 225 | auto isCryptAlgoEnabled() 226 | { 227 | return cryptAlgoInterface ? true : false; 228 | } 229 | 230 | void updateLastTransactionTime() 231 | { 232 | lastTime = std::chrono::steady_clock::now(); 233 | } 234 | 235 | /** 236 | * @brief Session Active Status 237 | * 238 | * Session Active status is decided upon the Session State and the last 239 | * transaction time is compared against the session inactivity timeout. 240 | * 241 | * @param[in] activeGrace - microseconds of idle time for active sessions 242 | * @param[in] setupGrace - microseconds of idle time for sessions in setup 243 | * 244 | */ 245 | bool isSessionActive(const std::chrono::microseconds& activeGrace, 246 | const std::chrono::microseconds& setupGrace) 247 | { 248 | auto currentTime = std::chrono::steady_clock::now(); 249 | auto elapsedMicros = 250 | std::chrono::duration_cast( 251 | currentTime - lastTime); 252 | 253 | State state = static_cast(this->state()); 254 | 255 | switch (state) 256 | { 257 | case State::active: 258 | if (elapsedMicros < activeGrace) 259 | { 260 | return true; 261 | } 262 | break; 263 | case State::setupInProgress: 264 | if (elapsedMicros < setupGrace) 265 | { 266 | return true; 267 | } 268 | break; 269 | case State::tearDownInProgress: 270 | break; 271 | default: 272 | break; 273 | } 274 | return false; 275 | } 276 | 277 | /** 278 | * @brief Session's Requested Maximum Privilege Level 279 | */ 280 | Privilege reqMaxPrivLevel; 281 | 282 | /** 283 | * @brief session's user & channel access details 284 | */ 285 | ipmi::PrivAccess sessionUserPrivAccess{}; 286 | ipmi::ChannelAccess sessionChannelAccess{}; 287 | 288 | SequenceNumbers sequenceNums; // Session Sequence Numbers 289 | std::string userName{}; // User Name 290 | 291 | /** @brief Socket channel for communicating with the remote client.*/ 292 | std::shared_ptr channelPtr; 293 | 294 | private: 295 | SessionID bmcSessionID = 0; // BMC Session ID 296 | SessionID remoteConsoleSessionID = 0; // Remote Console Session ID 297 | 298 | // Authentication Algorithm Interface for the Session 299 | std::unique_ptr authAlgoInterface; 300 | 301 | // Integrity Algorithm Interface for the Session 302 | std::unique_ptr integrityAlgoInterface = 303 | nullptr; 304 | 305 | // Confidentiality Algorithm Interface for the Session 306 | std::unique_ptr cryptAlgoInterface = nullptr; 307 | 308 | // Last Transaction Time 309 | decltype(std::chrono::steady_clock::now()) lastTime; 310 | }; 311 | 312 | } // namespace session 313 | -------------------------------------------------------------------------------- /command/payload_cmds.cpp: -------------------------------------------------------------------------------- 1 | #include "payload_cmds.hpp" 2 | 3 | #include "sessions_manager.hpp" 4 | #include "sol/sol_manager.hpp" 5 | #include "sol_cmds.hpp" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace sol 13 | { 14 | 15 | namespace command 16 | { 17 | 18 | std::vector activatePayload(const std::vector& inPayload, 19 | std::shared_ptr& handler) 20 | { 21 | auto request = 22 | reinterpret_cast(inPayload.data()); 23 | if (inPayload.size() != sizeof(*request)) 24 | { 25 | std::vector errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID}; 26 | return errorPayload; 27 | } 28 | 29 | std::vector outPayload(sizeof(ActivatePayloadResponse)); 30 | auto response = 31 | reinterpret_cast(outPayload.data()); 32 | 33 | response->completionCode = ipmi::ccSuccess; 34 | 35 | // SOL is the payload currently supported for activation. 36 | if (static_cast(message::PayloadType::SOL) != request->payloadType) 37 | { 38 | response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST; 39 | return outPayload; 40 | } 41 | 42 | sol::Manager::get().updateSOLParameter(ipmi::convertCurrentChannelNum( 43 | ipmi::currentChNum, getInterfaceIndex())); 44 | if (!sol::Manager::get().enable) 45 | { 46 | response->completionCode = IPMI_CC_PAYLOAD_TYPE_DISABLED; 47 | return outPayload; 48 | } 49 | 50 | // Only one instance of SOL is currently supported. 51 | if (request->payloadInstance != 1) 52 | { 53 | response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST; 54 | return outPayload; 55 | } 56 | 57 | auto session = session::Manager::get().getSession(handler->sessionID); 58 | 59 | if (!request->encryption && session->isCryptAlgoEnabled()) 60 | { 61 | response->completionCode = IPMI_CC_PAYLOAD_WITHOUT_ENCRYPTION; 62 | return outPayload; 63 | } 64 | 65 | if (session->currentPrivilege() < 66 | static_cast(sol::Manager::get().solMinPrivilege)) 67 | { 68 | response->completionCode = IPMI_CC_INSUFFICIENT_PRIVILEGE; 69 | return outPayload; 70 | } 71 | 72 | // Is SOL Payload enabled for this user & channel. 73 | auto userId = ipmi::ipmiUserGetUserId(session->userName); 74 | ipmi::PayloadAccess payloadAccess = {}; 75 | if ((ipmi::ipmiUserGetUserPayloadAccess(session->channelNum(), userId, 76 | payloadAccess) != 77 | ipmi::ccSuccess) || 78 | !(payloadAccess.stdPayloadEnables1[static_cast( 79 | message::PayloadType::SOL)])) 80 | { 81 | response->completionCode = IPMI_CC_PAYLOAD_TYPE_DISABLED; 82 | return outPayload; 83 | } 84 | 85 | auto status = sol::Manager::get().isPayloadActive(request->payloadInstance); 86 | if (status) 87 | { 88 | response->completionCode = IPMI_CC_PAYLOAD_ALREADY_ACTIVE; 89 | return outPayload; 90 | } 91 | 92 | // Set the current command's socket channel to the session 93 | handler->setChannelInSession(); 94 | 95 | // Start the SOL payload 96 | try 97 | { 98 | sol::Manager::get().startPayloadInstance(request->payloadInstance, 99 | handler->sessionID); 100 | } 101 | catch (const std::exception& e) 102 | { 103 | lg2::error("Failed to start SOL payload: {ERROR}", "ERROR", e); 104 | response->completionCode = IPMI_CC_UNSPECIFIED_ERROR; 105 | return outPayload; 106 | } 107 | 108 | response->inPayloadSize = endian::to_ipmi(MAX_PAYLOAD_SIZE); 109 | response->outPayloadSize = endian::to_ipmi(MAX_PAYLOAD_SIZE); 110 | response->portNum = endian::to_ipmi(IPMI_STD_PORT); 111 | 112 | // VLAN addressing is not used 113 | response->vlanNum = 0xFFFF; 114 | 115 | return outPayload; 116 | } 117 | 118 | std::vector deactivatePayload( 119 | const std::vector& inPayload, 120 | std::shared_ptr& handler) 121 | { 122 | auto request = 123 | reinterpret_cast(inPayload.data()); 124 | if (inPayload.size() != sizeof(*request)) 125 | { 126 | std::vector errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID}; 127 | return errorPayload; 128 | } 129 | 130 | std::vector outPayload(sizeof(DeactivatePayloadResponse)); 131 | auto response = 132 | reinterpret_cast(outPayload.data()); 133 | response->completionCode = ipmi::ccSuccess; 134 | 135 | // SOL is the payload currently supported for deactivation 136 | if (static_cast(message::PayloadType::SOL) != request->payloadType) 137 | { 138 | response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST; 139 | return outPayload; 140 | } 141 | 142 | // Only one instance of SOL is supported 143 | if (request->payloadInstance != 1) 144 | { 145 | response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST; 146 | return outPayload; 147 | } 148 | 149 | auto status = sol::Manager::get().isPayloadActive(request->payloadInstance); 150 | if (!status) 151 | { 152 | response->completionCode = IPMI_CC_PAYLOAD_DEACTIVATED; 153 | return outPayload; 154 | } 155 | 156 | auto currentSession = 157 | session::Manager::get().getSession(handler->sessionID); 158 | auto solSessionID = 159 | sol::Manager::get().getContext(request->payloadInstance).sessionID; 160 | auto solActiveSession = 161 | sol::Manager::get().getContext(request->payloadInstance).session; 162 | // The session owner or the ADMIN could deactivate the session 163 | if (currentSession->userName != solActiveSession->userName && 164 | currentSession->currentPrivilege() != 165 | static_cast(session::Privilege::ADMIN)) 166 | { 167 | response->completionCode = IPMI_CC_INSUFFICIENT_PRIVILEGE; 168 | return outPayload; 169 | } 170 | 171 | try 172 | { 173 | sol::Manager::get().stopPayloadInstance(request->payloadInstance); 174 | 175 | try 176 | { 177 | activating(request->payloadInstance, solSessionID); 178 | } 179 | catch (const std::exception& e) 180 | { 181 | lg2::info("Failed to call the activating method: {ERROR}", "ERROR", 182 | e); 183 | /* 184 | * In case session has been closed (like in the case of inactivity 185 | * timeout), then activating function would throw an exception, 186 | * since solSessionID is not found. As session is already closed, 187 | * returning IPMI status code for Payload already deactivated 188 | * as BMC automatically deactivates all active payloads when 189 | * session is terminated. 190 | */ 191 | response->completionCode = IPMI_CC_PAYLOAD_DEACTIVATED; 192 | return outPayload; 193 | } 194 | } 195 | catch (const std::exception& e) 196 | { 197 | lg2::error("Failed to call the getContext method: {ERROR}", "ERROR", e); 198 | response->completionCode = IPMI_CC_UNSPECIFIED_ERROR; 199 | return outPayload; 200 | } 201 | 202 | return outPayload; 203 | } 204 | 205 | std::vector getPayloadStatus( 206 | const std::vector& inPayload, 207 | std::shared_ptr& /* handler */) 208 | { 209 | auto request = 210 | reinterpret_cast(inPayload.data()); 211 | if (inPayload.size() != sizeof(*request)) 212 | { 213 | std::vector errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID}; 214 | return errorPayload; 215 | } 216 | 217 | std::vector outPayload(sizeof(GetPayloadStatusResponse)); 218 | auto response = 219 | reinterpret_cast(outPayload.data()); 220 | 221 | // SOL is the payload currently supported for payload status 222 | if (static_cast(message::PayloadType::SOL) != request->payloadType) 223 | { 224 | response->completionCode = IPMI_CC_UNSPECIFIED_ERROR; 225 | return outPayload; 226 | } 227 | 228 | response->completionCode = ipmi::ccSuccess; 229 | 230 | constexpr size_t maxSolPayloadInstances = 1; 231 | response->capacity = maxSolPayloadInstances; 232 | 233 | // Currently we support only one SOL session 234 | response->instance1 = sol::Manager::get().isPayloadActive(1); 235 | 236 | return outPayload; 237 | } 238 | 239 | std::vector getPayloadInfo( 240 | const std::vector& inPayload, 241 | std::shared_ptr& /* handler */) 242 | { 243 | auto request = 244 | reinterpret_cast(inPayload.data()); 245 | 246 | if (inPayload.size() != sizeof(*request)) 247 | { 248 | std::vector errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID}; 249 | return errorPayload; 250 | } 251 | 252 | std::vector outPayload(sizeof(GetPayloadInfoResponse)); 253 | auto response = 254 | reinterpret_cast(outPayload.data()); 255 | 256 | // SOL is the payload currently supported for payload status & only one 257 | // instance of SOL is supported. 258 | if (static_cast(message::PayloadType::SOL) != 259 | request->payloadType || 260 | request->payloadInstance != 1) 261 | { 262 | response->completionCode = IPMI_CC_INVALID_FIELD_REQUEST; 263 | return outPayload; 264 | } 265 | auto status = sol::Manager::get().isPayloadActive(request->payloadInstance); 266 | 267 | if (status) 268 | { 269 | auto& context = 270 | sol::Manager::get().getContext(request->payloadInstance); 271 | response->sessionID = context.sessionID; 272 | } 273 | else 274 | { 275 | // No active payload - return session id as 0 276 | response->sessionID = 0; 277 | } 278 | response->completionCode = ipmi::ccSuccess; 279 | return outPayload; 280 | } 281 | 282 | } // namespace command 283 | 284 | } // namespace sol 285 | -------------------------------------------------------------------------------- /command/channel_auth.cpp: -------------------------------------------------------------------------------- 1 | #include "channel_auth.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | namespace command 20 | { 21 | 22 | using namespace phosphor::logging; 23 | using namespace sdbusplus::xyz::openbmc_project::Common::Error; 24 | using Json = nlohmann::json; 25 | 26 | std::vector GetChannelCapabilities( 27 | const std::vector& inPayload, 28 | std::shared_ptr& /* handler */) 29 | { 30 | auto request = 31 | reinterpret_cast(inPayload.data()); 32 | if (inPayload.size() != sizeof(*request)) 33 | { 34 | std::vector errorPayload{IPMI_CC_REQ_DATA_LEN_INVALID}; 35 | return errorPayload; 36 | } 37 | constexpr unsigned int channelMask = 0x0f; 38 | uint8_t chNum = ipmi::convertCurrentChannelNum( 39 | request->channelNumber & channelMask, getInterfaceIndex()); 40 | 41 | if (!ipmi::isValidChannel(chNum) || 42 | (ipmi::EChannelSessSupported::none == 43 | ipmi::getChannelSessionSupport(chNum)) || 44 | !ipmi::isValidPrivLimit(request->reqMaxPrivLevel)) 45 | { 46 | std::vector errorPayload{IPMI_CC_INVALID_FIELD_REQUEST}; 47 | return errorPayload; 48 | } 49 | 50 | std::vector outPayload(sizeof(GetChannelCapabilitiesResp)); 51 | auto response = 52 | reinterpret_cast(outPayload.data()); 53 | 54 | // A canned response, since there is no user and channel management. 55 | response->completionCode = ipmi::ccSuccess; 56 | 57 | response->channelNumber = chNum; 58 | 59 | response->ipmiVersion = 1; // IPMI v2.0 extended capabilities available. 60 | response->reserved1 = 0; 61 | response->oem = 0; 62 | response->straightKey = 0; 63 | response->reserved2 = 0; 64 | response->md5 = 0; 65 | response->md2 = 0; 66 | 67 | response->reserved3 = 0; 68 | response->KGStatus = 0; // KG is set to default 69 | response->perMessageAuth = 0; // Per-message Authentication is enabled 70 | response->userAuth = 0; // User Level Authentication is enabled 71 | uint8_t maxChUsers = 0; 72 | uint8_t enabledUsers = 0; 73 | uint8_t fixedUsers = 0; 74 | ipmi::ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers); 75 | 76 | response->nonNullUsers = enabledUsers > 0 ? 1 : 0; // Non-null usernames 77 | response->nullUsers = 0; // Null usernames disabled 78 | response->anonymousLogin = 0; // Anonymous Login disabled 79 | 80 | response->reserved4 = 0; 81 | response->extCapabilities = 0x2; // Channel supports IPMI v2.0 connections 82 | 83 | response->oemID[0] = 0; 84 | response->oemID[1] = 0; 85 | response->oemID[2] = 0; 86 | response->oemAuxillary = 0; 87 | return outPayload; 88 | } 89 | 90 | static constexpr const char* configFile = 91 | "/usr/share/ipmi-providers/cipher_list.json"; 92 | static constexpr const char* cipher = "cipher"; 93 | static constexpr uint8_t stdCipherSuite = 0xC0; 94 | static constexpr uint8_t oemCipherSuite = 0xC1; 95 | static constexpr const char* oem = "oemiana"; 96 | static constexpr const char* auth = "authentication"; 97 | static constexpr const char* integrity = "integrity"; 98 | static constexpr uint8_t integrityTag = 0x40; 99 | static constexpr const char* conf = "confidentiality"; 100 | static constexpr uint8_t confTag = 0x80; 101 | 102 | /** @brief Get the supported Cipher records 103 | * 104 | * The cipher records are read from the JSON file and converted into 105 | * 1. cipher suite record format mentioned in the IPMI specification. The 106 | * records can be either OEM or standard cipher. Each json entry is parsed and 107 | * converted into the cipher record format and pushed into the vector. 108 | * 2. Algorithms listed in vector format 109 | * 110 | * @return pair of vector containing 1. all the cipher suite records. 2. 111 | * Algorithms supported 112 | * 113 | */ 114 | static std::pair, std::vector> getCipherRecords() 115 | { 116 | std::vector cipherRecords; 117 | std::vector supportedAlgorithmRecords; 118 | // create set to get the unique supported algorithms 119 | std::set supportedAlgorithmSet; 120 | 121 | std::ifstream jsonFile(configFile); 122 | if (!jsonFile.is_open()) 123 | { 124 | lg2::error("Channel Cipher suites file not found: {ERROR}", "ERROR", 125 | strerror(errno)); 126 | elog(); 127 | } 128 | 129 | auto data = Json::parse(jsonFile, nullptr, false); 130 | if (data.is_discarded()) 131 | { 132 | lg2::error("Parsing channel cipher suites JSON failed: {ERROR}", 133 | "ERROR", strerror(errno)); 134 | elog(); 135 | } 136 | 137 | for (const auto& record : data) 138 | { 139 | if (record.find(oem) != record.end()) 140 | { 141 | // OEM cipher suite - 0xC1 142 | cipherRecords.push_back(oemCipherSuite); 143 | // Cipher Suite ID 144 | cipherRecords.push_back(record.value(cipher, 0)); 145 | // OEM IANA - 3 bytes 146 | cipherRecords.push_back(record.value(oem, 0)); 147 | cipherRecords.push_back(record.value(oem, 0) >> 8); 148 | cipherRecords.push_back(record.value(oem, 0) >> 16); 149 | } 150 | else 151 | { 152 | // Standard cipher suite - 0xC0 153 | cipherRecords.push_back(stdCipherSuite); 154 | // Cipher Suite ID 155 | cipherRecords.push_back(record.value(cipher, 0)); 156 | } 157 | 158 | // Authentication algorithm number 159 | cipherRecords.push_back(record.value(auth, 0)); 160 | supportedAlgorithmSet.insert(record.value(auth, 0)); 161 | 162 | // Integrity algorithm number 163 | cipherRecords.push_back(record.value(integrity, 0) | integrityTag); 164 | supportedAlgorithmSet.insert(record.value(integrity, 0) | integrityTag); 165 | 166 | // Confidentiality algorithm number 167 | cipherRecords.push_back(record.value(conf, 0) | confTag); 168 | supportedAlgorithmSet.insert(record.value(conf, 0) | confTag); 169 | } 170 | 171 | // copy the set to supportedAlgorithmRecord which is vector based. 172 | std::copy(supportedAlgorithmSet.begin(), supportedAlgorithmSet.end(), 173 | std::back_inserter(supportedAlgorithmRecords)); 174 | 175 | return std::make_pair(cipherRecords, supportedAlgorithmRecords); 176 | } 177 | 178 | /** @brief this command is used to look up what authentication, integrity, 179 | * confidentiality algorithms are supported. 180 | * 181 | * @ param inPayload - vector of input data 182 | * @ param handler - pointer to handler 183 | * 184 | * @returns ipmi completion code plus response data 185 | * - vector of response data: cc, channel, record data 186 | **/ 187 | std::vector getChannelCipherSuites( 188 | const std::vector& inPayload, 189 | std::shared_ptr& /* handler */) 190 | { 191 | const auto errorResponse = [](uint8_t cc) { 192 | std::vector rsp(1); 193 | rsp[0] = cc; 194 | return rsp; 195 | }; 196 | 197 | static constexpr size_t getChannelCipherSuitesReqLen = 3; 198 | if (inPayload.size() != getChannelCipherSuitesReqLen) 199 | { 200 | return errorResponse(IPMI_CC_REQ_DATA_LEN_INVALID); 201 | } 202 | 203 | static constexpr uint8_t channelMask = 0x0f; 204 | uint8_t channelNumber = inPayload[0] & channelMask; 205 | if (channelNumber != inPayload[0]) 206 | { 207 | return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST); 208 | } 209 | static constexpr uint8_t payloadMask = 0x3f; 210 | uint8_t payloadType = inPayload[1] & payloadMask; 211 | if (payloadType != inPayload[1]) 212 | { 213 | return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST); 214 | } 215 | static constexpr uint8_t indexMask = 0x3f; 216 | uint8_t listIndex = inPayload[2] & indexMask; 217 | static constexpr uint8_t algoSelectShift = 7; 218 | uint8_t algoSelectBit = inPayload[2] >> algoSelectShift; 219 | if ((listIndex | (algoSelectBit << algoSelectShift)) != inPayload[2]) 220 | { 221 | return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST); 222 | } 223 | 224 | static std::vector cipherRecords; 225 | static std::vector supportedAlgorithms; 226 | static bool recordInit = false; 227 | 228 | uint8_t rspChannel = 229 | ipmi::convertCurrentChannelNum(channelNumber, getInterfaceIndex()); 230 | 231 | if (!ipmi::isValidChannel(rspChannel)) 232 | { 233 | return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST); 234 | } 235 | if (!ipmi::isValidPayloadType(static_cast(payloadType))) 236 | { 237 | lg2::debug("Get channel cipher suites - Invalid payload type: {ERROR}", 238 | "ERROR", strerror(errno)); 239 | constexpr uint8_t ccPayloadTypeNotSupported = 0x80; 240 | return errorResponse(ccPayloadTypeNotSupported); 241 | } 242 | 243 | if (!recordInit) 244 | { 245 | try 246 | { 247 | std::tie(cipherRecords, supportedAlgorithms) = getCipherRecords(); 248 | recordInit = true; 249 | } 250 | catch (const std::exception& e) 251 | { 252 | return errorResponse(IPMI_CC_UNSPECIFIED_ERROR); 253 | } 254 | } 255 | 256 | const std::vector& records = 257 | algoSelectBit ? cipherRecords : supportedAlgorithms; 258 | static constexpr auto respSize = 16; 259 | 260 | // Session support is available in active LAN channels. 261 | if ((ipmi::getChannelSessionSupport(rspChannel) == 262 | ipmi::EChannelSessSupported::none) || 263 | !(ipmi::doesDeviceExist(rspChannel))) 264 | { 265 | lg2::debug("Get channel cipher suites - Device does not exist:{ERROR}", 266 | "ERROR", strerror(errno)); 267 | return errorResponse(IPMI_CC_INVALID_FIELD_REQUEST); 268 | } 269 | 270 | // List index(00h-3Fh), 0h selects the first set of 16, 1h selects the next 271 | // set of 16 and so on. 272 | 273 | // Calculate the number of record data bytes to be returned. 274 | auto start = 275 | std::min(static_cast(listIndex) * respSize, records.size()); 276 | auto end = std::min((static_cast(listIndex) * respSize) + respSize, 277 | records.size()); 278 | auto size = end - start; 279 | 280 | std::vector rsp; 281 | rsp.push_back(ipmi::ccSuccess); 282 | rsp.push_back(rspChannel); 283 | std::copy_n(records.data() + start, size, std::back_inserter(rsp)); 284 | 285 | return rsp; 286 | } 287 | 288 | } // namespace command 289 | -------------------------------------------------------------------------------- /command/payload_cmds.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "message_handler.hpp" 4 | 5 | #include 6 | 7 | namespace sol 8 | { 9 | 10 | namespace command 11 | { 12 | 13 | constexpr uint8_t IPMI_CC_PAYLOAD_ALREADY_ACTIVE = 0x80; 14 | constexpr uint8_t IPMI_CC_PAYLOAD_TYPE_DISABLED = 0x81; 15 | constexpr uint8_t IPMI_CC_PAYLOAD_ACTIVATION_LIMIT = 0x82; 16 | constexpr uint8_t IPMI_CC_PAYLOAD_WITH_ENCRYPTION = 0x83; 17 | constexpr uint8_t IPMI_CC_PAYLOAD_WITHOUT_ENCRYPTION = 0x84; 18 | 19 | /** @struct ActivatePayloadRequest 20 | * 21 | * IPMI payload for Activate Payload command request. 22 | */ 23 | struct ActivatePayloadRequest 24 | { 25 | #if BYTE_ORDER == LITTLE_ENDIAN 26 | uint8_t payloadType:6; //!< Payload type. 27 | uint8_t reserved1:2; //!< Reserved. 28 | #endif 29 | 30 | #if BYTE_ORDER == BIG_ENDIAN 31 | uint8_t reserved1:2; //!< Payload type. 32 | uint8_t payloadType:6; //!< Payload type. 33 | #endif 34 | 35 | #if BYTE_ORDER == LITTLE_ENDIAN 36 | uint8_t payloadInstance:4; //!< Payload instance. 37 | uint8_t reserved2:4; //!< Reserved. 38 | #endif 39 | 40 | #if BYTE_ORDER == BIG_ENDIAN 41 | uint8_t reserved2:4; //!< Reserved. 42 | uint8_t payloadInstance:4; //!< Payload instance. 43 | #endif 44 | 45 | /** @brief The following Auxiliary Request Data applies only for payload 46 | * SOL only. 47 | */ 48 | #if BYTE_ORDER == LITTLE_ENDIAN 49 | uint8_t reserved4:1; //!< Reserved. 50 | uint8_t handshake:1; //!< SOL startup handshake. 51 | uint8_t alert:2; //!< Shared serial alert behavior. 52 | uint8_t reserved3:1; //!< Reserved. 53 | uint8_t testMode:1; //!< Test mode. 54 | uint8_t auth:1; //!< If true, activate payload with authentication. 55 | uint8_t encryption:1; //!< If true, activate payload with encryption. 56 | #endif 57 | 58 | #if BYTE_ORDER == BIG_ENDIAN 59 | uint8_t encryption:1; //!< If true, activate payload with encryption. 60 | uint8_t auth:1; //!< If true, activate payload with authentication. 61 | uint8_t testMode:1; //!< Test mode. 62 | uint8_t reserved3:1; //!< Reserved. 63 | uint8_t alert:2; //!< Shared serial alert behavior. 64 | uint8_t handshake:1; //!< SOL startup handshake. 65 | uint8_t reserved4:1; //!< Reserved. 66 | #endif 67 | 68 | uint8_t reserved5; //!< Reserved. 69 | uint8_t reserved6; //!< Reserved. 70 | uint8_t reserved7; //!< Reserved. 71 | } __attribute__((packed)); 72 | 73 | /** @struct ActivatePayloadResponse 74 | * 75 | * IPMI payload for Activate Payload command response. 76 | */ 77 | struct ActivatePayloadResponse 78 | { 79 | uint8_t completionCode; //!< Completion code. 80 | uint8_t reserved1; //!< Reserved. 81 | uint8_t reserved2; //!< Reserved. 82 | uint8_t reserved3; //!< Reserved. 83 | 84 | // Test Mode 85 | #if BYTE_ORDER == LITTLE_ENDIAN 86 | uint8_t testMode:1; //!< Test mode. 87 | uint8_t reserved4:7; //!< Reserved. 88 | #endif 89 | 90 | #if BYTE_ORDER == BIG_ENDIAN 91 | uint8_t reserved4:7; //!< Reserved. 92 | uint8_t testMode:1; //!< Test mode. 93 | #endif 94 | 95 | uint16_t inPayloadSize; //!< Inbound payload size 96 | uint16_t outPayloadSize; //!< Outbound payload size. 97 | uint16_t portNum; //!< Payload UDP port number. 98 | uint16_t vlanNum; //!< Payload VLAN number. 99 | } __attribute__((packed)); 100 | 101 | /** @brief Activate Payload Command. 102 | * 103 | * This command is used for activating and deactivating a payload type under a 104 | * given IPMI session. The UDP Port number for SOL is the same as the port that 105 | * was used to establish the IPMI session. 106 | * 107 | * @param[in] inPayload - Request data for the command. 108 | * @param[in] handler - Reference to the message handler. 109 | * 110 | * @return Response data for the command 111 | */ 112 | std::vector activatePayload( 113 | const std::vector& inPayload, 114 | std::shared_ptr& handler); 115 | 116 | constexpr uint8_t IPMI_CC_PAYLOAD_DEACTIVATED = 0x80; 117 | 118 | /** @struct DeactivatePayloadRequest 119 | * 120 | * IPMI payload for Deactivate Payload command request. 121 | */ 122 | struct DeactivatePayloadRequest 123 | { 124 | #if BYTE_ORDER == LITTLE_ENDIAN 125 | uint8_t payloadType:6; //!< Payload type. 126 | uint8_t reserved1:2; //!< Reserved. 127 | #endif 128 | 129 | #if BYTE_ORDER == BIG_ENDIAN 130 | uint8_t reserved1:2; //!< Payload type. 131 | uint8_t payloadType:6; //!< Reserved. 132 | #endif 133 | 134 | #if BYTE_ORDER == LITTLE_ENDIAN 135 | uint8_t payloadInstance:4; //!< Payload instance. 136 | uint8_t reserved2:4; //!< Reserved. 137 | #endif 138 | 139 | #if BYTE_ORDER == BIG_ENDIAN 140 | uint8_t reserved2:4; //!< Reserved. 141 | uint8_t payloadInstance:4; //!< Payload instance. 142 | #endif 143 | 144 | /** @brief No auxiliary data for payload type SOL */ 145 | uint8_t auxData1; //!< Auxiliary data 1 146 | uint8_t auxData2; //!< Auxiliary data 2 147 | uint8_t auxData3; //!< Auxiliary data 3 148 | uint8_t auxData4; //!< Auxiliary data 4 149 | } __attribute__((packed)); 150 | 151 | /** @struct DeactivatePayloadResponse 152 | * 153 | * IPMI payload for Deactivate Payload Command response. 154 | */ 155 | struct DeactivatePayloadResponse 156 | { 157 | uint8_t completionCode; //!< Completion code 158 | } __attribute__((packed)); 159 | 160 | /** @brief Deactivate Payload Command. 161 | * 162 | * This command is used to terminate use of a given payload on an IPMI session. 163 | * This type of traffic then becomes freed for activation by another session, 164 | * or for possible re-activation under the present session.The Deactivate 165 | * Payload command does not cause the session to be terminated. The Close 166 | * Session command should be used for that purpose. A remote console 167 | * terminating a application does not need to explicitly deactivate payload(s) 168 | * prior to session. When a session terminates all payloads that were active 169 | * under that session are automatically deactivated by the BMC. 170 | * 171 | * @param[in] inPayload - Request data for the command. 172 | * @param[in] handler - Reference to the message handler. 173 | * 174 | * @return Response data for the command. 175 | */ 176 | std::vector deactivatePayload( 177 | const std::vector& inPayload, 178 | std::shared_ptr& handler); 179 | 180 | /** @struct GetPayloadStatusRequest 181 | * 182 | * IPMI payload for Get Payload Activation Status command request. 183 | */ 184 | struct GetPayloadStatusRequest 185 | { 186 | uint8_t payloadType; //!< Payload type 187 | } __attribute__((packed)); 188 | 189 | /** @struct GetPayloadStatusResponse 190 | * 191 | * IPMI payload for Get Payload Activation Status command response. 192 | */ 193 | struct GetPayloadStatusResponse 194 | { 195 | uint8_t completionCode; //!< Completion code. 196 | 197 | uint8_t capacity; //!< Instance capacity. 198 | 199 | /* @brief Activation Status. */ 200 | #if BYTE_ORDER == LITTLE_ENDIAN 201 | uint8_t instance1:1; //!< If true, Instance 1 is activated. 202 | uint8_t instance2:1; //!< If true, Instance 2 is activated. 203 | uint8_t instance3:1; //!< If true, Instance 3 is activated. 204 | uint8_t instance4:1; //!< If true, Instance 4 is activated. 205 | uint8_t instance5:1; //!< If true, Instance 5 is activated. 206 | uint8_t instance6:1; //!< If true, Instance 6 is activated. 207 | uint8_t instance7:1; //!< If true, Instance 7 is activated. 208 | uint8_t instance8:1; //!< If true, Instance 8 is activated. 209 | #endif 210 | 211 | #if BYTE_ORDER == BIG_ENDIAN 212 | uint8_t instance8:1; //!< If true, Instance 8 is activated. 213 | uint8_t instance7:1; //!< If true, Instance 7 is activated. 214 | uint8_t instance6:1; //!< If true, Instance 6 is activated. 215 | uint8_t instance5:1; //!< If true, Instance 5 is activated. 216 | uint8_t instance4:1; //!< If true, Instance 4 is activated. 217 | uint8_t instance3:1; //!< If true, Instance 3 is activated. 218 | uint8_t instance2:1; //!< If true, Instance 2 is activated. 219 | uint8_t instance1:1; //!< If true, Instance 1 is activated. 220 | #endif 221 | 222 | #if BYTE_ORDER == LITTLE_ENDIAN 223 | uint8_t instance9:1; //!< If true, Instance 9 is activated. 224 | uint8_t instance10:1; //!< If true, Instance 10 is activated. 225 | uint8_t instance11:1; //!< If true, Instance 11 is activated. 226 | uint8_t instance12:1; //!< If true, Instance 12 is activated. 227 | uint8_t instance13:1; //!< If true, Instance 13 is activated. 228 | uint8_t instance14:1; //!< If true, Instance 14 is activated. 229 | uint8_t instance15:1; //!< If true, Instance 15 is activated. 230 | uint8_t instance16:1; //!< If true, Instance 16 is activated. 231 | #endif 232 | 233 | #if BYTE_ORDER == BIG_ENDIAN 234 | uint8_t instance16:1; //!< If true, Instance 16 is activated. 235 | uint8_t instance15:1; //!< If true, Instance 15 is activated. 236 | uint8_t instance14:1; //!< If true, Instance 14 is activated. 237 | uint8_t instance13:1; //!< If true, Instance 13 is activated. 238 | uint8_t instance12:1; //!< If true, Instance 12 is activated. 239 | uint8_t instance11:1; //!< If true, Instance 11 is activated. 240 | uint8_t instance10:1; //!< If true, Instance 10 is activated. 241 | uint8_t instance9:1; //!< If true, Instance 9 is activated. 242 | #endif 243 | } __attribute__((packed)); 244 | 245 | /** @brief Get Payload Activation Status Command. 246 | * 247 | * This command returns how many instances of a given payload type are 248 | * presently activated, and how many total instances can be activated. 249 | * 250 | * @param[in] inPayload - Request Data for the command. 251 | * @param[in] handler - Reference to the Message Handler. 252 | * 253 | * @return Response data for the command 254 | */ 255 | std::vector getPayloadStatus( 256 | const std::vector& inPayload, 257 | std::shared_ptr& handler); 258 | 259 | /** @struct GetPayloadInfoRequest 260 | * 261 | * IPMI payload for Get Payload Instance info command request. 262 | */ 263 | struct GetPayloadInfoRequest 264 | { 265 | uint8_t payloadType; //!< Payload type 266 | uint8_t payloadInstance; //!< Payload instance 267 | } __attribute__((packed)); 268 | 269 | /** @struct GetPayloadInfoResponse 270 | * 271 | * IPMI payload for Get Payload Instance info command response. 272 | */ 273 | struct GetPayloadInfoResponse 274 | { 275 | uint8_t completionCode; //!< Completion code. 276 | uint32_t sessionID; //!< Session ID 277 | uint8_t portNumber; //!< Port number 278 | uint8_t reserved[7]; //!< Reserved 279 | } __attribute__((packed)); 280 | 281 | /** @brief Get Payload Instance Info Command. 282 | * 283 | * This command returns information about a specific instance of a payload 284 | * type. Session ID is returned by this command 285 | * 286 | * @param[in] inPayload - Request Data for the command. 287 | * @param[in] handler - Reference to the Message Handler. 288 | * 289 | * @return Response data for the command 290 | */ 291 | std::vector getPayloadInfo(const std::vector& inPayload, 292 | std::shared_ptr& handler); 293 | 294 | } // namespace command 295 | 296 | } // namespace sol 297 | --------------------------------------------------------------------------------