#include "sessions_manager.hpp" #include "main.hpp" #include "session.hpp" #include #include #include #include #include #include #include namespace session { static std::array ipmiNetworkChannelNumList = {0}; void Manager::setNetworkInstance(void) { uint8_t index = 0, ch = 1; // Constructing net-ipmid instances list based on channel info // valid channel start from 1 to 15 and assuming max 4 LAN channel // supported while (ch < ipmi::maxIpmiChannels && index < session::maxNetworkInstanceSupported) { ipmi::ChannelInfo chInfo; ipmi::getChannelInfo(ch, chInfo); if (static_cast(chInfo.mediumType) == ipmi::EChannelMediumType::lan8032) { if (getInterfaceIndex() == ch) { ipmiNetworkInstance = index; } ipmiNetworkChannelNumList[index] = ch; index++; } ch++; } } uint8_t Manager::getNetworkInstance(void) { return ipmiNetworkInstance; } void Manager::managerInit(const std::string& channel) { /* * Session ID is 0000_0000h for messages that are sent outside the session. * The session setup commands are sent on this session, so when the session * manager comes up, is creates the Session ID 0000_0000h. It is active * through the lifetime of the Session Manager. */ objManager = std::make_unique( *getSdBus(), session::sessionManagerRootPath); auto objPath = std::string(session::sessionManagerRootPath) + "/" + channel + "/0"; chName = channel; setNetworkInstance(); sessionsMap.emplace( 0, std::make_shared(*getSdBus(), objPath.c_str(), 0, 0, 0)); // set up the timer for clearing out stale sessions scheduleSessionCleaner(std::chrono::microseconds(3 * 1000 * 1000)); } std::shared_ptr Manager::startSession(SessionID remoteConsoleSessID, Privilege priv, cipher::rakp_auth::Algorithms authAlgo, cipher::integrity::Algorithms intAlgo, cipher::crypt::Algorithms cryptAlgo) { std::shared_ptr session = nullptr; SessionID bmcSessionID = 0; cleanStaleEntries(); // set up the timer for monitoring this session scheduleSessionCleaner(std::chrono::microseconds(1 * 1000 * 1000)); uint8_t sessionHandle = 0; auto activeSessions = sessionsMap.size() - session::maxSessionlessCount; if (activeSessions < maxSessionHandles) { do { bmcSessionID = (crypto::prng::rand()); bmcSessionID &= session::multiIntfaceSessionIDMask; // In sessionID , BIT 31 BIT30 are used for netipmid instance bmcSessionID |= static_cast(ipmiNetworkInstance) << 30; /* * Every IPMI Session has two ID's attached to it Remote Console * Session ID and BMC Session ID. The remote console ID is passed * along with the Open Session request command. The BMC session ID * is the key for the session map and is generated using std::rand. * There is a rare chance for collision of BMC session ID, so the * following check validates that. In the case of collision the * created session is reset and a new session is created for * validating collision. */ auto iterator = sessionsMap.find(bmcSessionID); if (iterator != sessionsMap.end()) { // Detected BMC Session ID collisions continue; } else { break; } } while (1); sessionHandle = storeSessionHandle(bmcSessionID); if (!sessionHandle) { throw std::runtime_error( "Invalid sessionHandle - No sessionID slot "); } sessionHandle &= session::multiIntfaceSessionHandleMask; // In sessionID , BIT 31 BIT30 are used for netipmid instance sessionHandle |= static_cast(ipmiNetworkInstance) << 6; std::stringstream sstream; sstream << std::hex << bmcSessionID; std::stringstream shstream; shstream << std::hex << (int)sessionHandle; auto objPath = std::string(session::sessionManagerRootPath) + "/" + chName + "/" + sstream.str() + "_" + shstream.str(); session = std::make_shared(*getSdBus(), objPath.c_str(), remoteConsoleSessID, bmcSessionID, static_cast(priv)); // Set the Authentication Algorithm switch (authAlgo) { case cipher::rakp_auth::Algorithms::RAKP_HMAC_SHA1: { session->setAuthAlgo( std::make_unique(intAlgo, cryptAlgo)); break; } case cipher::rakp_auth::Algorithms::RAKP_HMAC_SHA256: { session->setAuthAlgo( std::make_unique(intAlgo, cryptAlgo)); break; } default: { throw std::runtime_error("Invalid Authentication Algorithm"); } } sessionsMap.emplace(bmcSessionID, session); session->sessionHandle(sessionHandle); return session; } lg2::info("No free RMCP+ sessions left"); throw std::runtime_error("No free sessions left"); } bool Manager::stopSession(SessionID bmcSessionID) { auto iter = sessionsMap.find(bmcSessionID); if (iter != sessionsMap.end()) { iter->second->state( static_cast(session::State::tearDownInProgress)); return true; } else { return false; } } std::shared_ptr Manager::getSession(SessionID sessionID, RetrieveOption option) { switch (option) { case RetrieveOption::BMC_SESSION_ID: { auto iter = sessionsMap.find(sessionID); if (iter != sessionsMap.end()) { return iter->second; } break; } case RetrieveOption::RC_SESSION_ID: { auto iter = std::find_if( sessionsMap.begin(), sessionsMap.end(), [sessionID]( const std::pair>& in) -> bool { return sessionID == in.second->getRCSessionID(); }); if (iter != sessionsMap.end()) { return iter->second; } break; } default: throw std::runtime_error("Invalid retrieval option"); } throw std::runtime_error("Session ID not found"); } void Manager::cleanStaleEntries() { // with overflow = min(1, max - active sessions) // active idle time in seconds = 60 / overflow^3 constexpr int baseIdleMicros = 60 * 1000 * 1000; // no +1 for the zero session here because this is just active sessions int sessionDivisor = getActiveSessionCount() - session::maxSessionCountPerChannel; sessionDivisor = std::max(0, sessionDivisor) + 1; sessionDivisor = sessionDivisor * sessionDivisor * sessionDivisor; int activeMicros = baseIdleMicros / sessionDivisor; // with overflow = min(1, max - total sessions) // setup idle time in seconds = max(3, 60 / overflow^3) // +1 for the zero session here because size() counts that too int setupDivisor = sessionsMap.size() - (session::maxSessionCountPerChannel + 1); setupDivisor = std::max(0, setupDivisor) + 1; setupDivisor = setupDivisor * setupDivisor * setupDivisor; constexpr int maxSetupMicros = 3 * 1000 * 1000; int setupMicros = std::min(maxSetupMicros, baseIdleMicros / setupDivisor); std::chrono::microseconds activeGrace(activeMicros); std::chrono::microseconds setupGrace(setupMicros); for (auto iter = sessionsMap.begin(); iter != sessionsMap.end();) { auto session = iter->second; // special handling for sessionZero if (session->getBMCSessionID() == session::sessionZero) { iter++; continue; } if (!(session->isSessionActive(activeGrace, setupGrace))) { lg2::info( "Removing idle IPMI LAN session, id: {ID}, handler: {HANDLE}", "ID", session->getBMCSessionID(), "HANDLE", getSessionHandle(session->getBMCSessionID())); sessionHandleMap[getSessionHandle(session->getBMCSessionID())] = 0; iter = sessionsMap.erase(iter); } else { iter++; } } if (sessionsMap.size() > 1) { constexpr int maxCleanupDelay = 1 * 1000 * 1000; std::chrono::microseconds cleanupDelay( std::min(setupMicros, maxCleanupDelay)); scheduleSessionCleaner(cleanupDelay); } } uint8_t Manager::storeSessionHandle(SessionID bmcSessionID) { // Handler index 0 is reserved for invalid session. // index starts with 1, for direct usage. Index 0 reserved for (size_t i = 1; i < session::maxSessionHandles; i++) { if (sessionHandleMap[i] == 0) { sessionHandleMap[i] = bmcSessionID; return i; } } return 0; } uint32_t Manager::getSessionIDbyHandle(uint8_t sessionHandle) const { if (sessionHandle < session::maxSessionHandles) { return sessionHandleMap[sessionHandle]; } return 0; } uint8_t Manager::getSessionHandle(SessionID bmcSessionID) const { // Handler index 0 is reserved for invalid session. // index starts with 1, for direct usage. Index 0 reserved for (size_t i = 1; i < session::maxSessionHandles; i++) { if (sessionHandleMap[i] == bmcSessionID) { return (i); } } return 0; } uint8_t Manager::getActiveSessionCount() const { return (std::count_if( sessionsMap.begin(), sessionsMap.end(), [](const std::pair>& in) -> bool { return in.second->state() == static_cast(session::State::active); })); } void Manager::scheduleSessionCleaner(const std::chrono::microseconds& when) { std::chrono::duration expTime = timer.expiry() - boost::asio::steady_timer::clock_type::now(); if (expTime > std::chrono::microseconds(0) && expTime < when) { // if timer has not already expired AND requested timeout is greater // than current timeout then ignore this new requested timeout return; } timer.expires_after(when); timer.async_wait([this](const boost::system::error_code& ec) { if (!ec) { cleanStaleEntries(); } }); } } // namespace session