blob: c18bf808b2b2aba8795ef33336cb0493194f9c77 [file] [log] [blame]
#include "sessions_manager.hpp"
#include "main.hpp"
#include "session.hpp"
#include <phosphor-logging/lg2.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <user_channel/channel_layer.hpp>
#include <algorithm>
#include <cstdlib>
#include <iomanip>
#include <memory>
namespace session
{
static std::array<uint8_t, session::maxNetworkInstanceSupported>
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<ipmi::EChannelMediumType>(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<sdbusplus::server::manager_t>(
*getSdBus(), session::sessionManagerRootPath);
auto objPath =
std::string(session::sessionManagerRootPath) + "/" + channel + "/0";
chName = channel;
setNetworkInstance();
sessionsMap.emplace(
0, std::make_shared<Session>(*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<Session>
Manager::startSession(SessionID remoteConsoleSessID, Privilege priv,
cipher::rakp_auth::Algorithms authAlgo,
cipher::integrity::Algorithms intAlgo,
cipher::crypt::Algorithms cryptAlgo)
{
std::shared_ptr<Session> 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<uint32_t>(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<uint8_t>(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<Session>(*getSdBus(), objPath.c_str(),
remoteConsoleSessID, bmcSessionID,
static_cast<uint8_t>(priv));
// Set the Authentication Algorithm
switch (authAlgo)
{
case cipher::rakp_auth::Algorithms::RAKP_HMAC_SHA1:
{
session->setAuthAlgo(
std::make_unique<cipher::rakp_auth::AlgoSHA1>(intAlgo,
cryptAlgo));
break;
}
case cipher::rakp_auth::Algorithms::RAKP_HMAC_SHA256:
{
session->setAuthAlgo(
std::make_unique<cipher::rakp_auth::AlgoSHA256>(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<uint8_t>(session::State::tearDownInProgress));
return true;
}
else
{
return false;
}
}
std::shared_ptr<Session> 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<const uint32_t, std::shared_ptr<Session>>&
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<const uint32_t, std::shared_ptr<Session>>& in)
-> bool {
return in.second->state() ==
static_cast<uint8_t>(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