blob: 50505aa87a5ca0816bb391cb2575855b50445555 [file] [log] [blame]
/*
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#include "channel_mgmt.hpp"
#include "apphandler.hpp"
#include "user_layer.hpp"
#include <ifaddrs.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus/match.hpp>
#include <sdbusplus/server/object.hpp>
#include <cerrno>
#include <exception>
#include <filesystem>
#include <fstream>
#include <unordered_map>
namespace ipmi
{
using namespace phosphor::logging;
static constexpr const char* channelAccessDefaultFilename =
"/usr/share/ipmi-providers/channel_access.json";
static constexpr const char* channelConfigDefaultFilename =
"/usr/share/ipmi-providers/channel_config.json";
static constexpr const char* channelNvDataFilename =
"/var/lib/ipmi/channel_access_nv.json";
static constexpr const char* channelVolatileDataFilename =
"/run/ipmi/channel_access_volatile.json";
// TODO: Get the service name dynamically..
static constexpr const char* networkIntfServiceName =
"xyz.openbmc_project.Network";
static constexpr const char* networkIntfObjectBasePath =
"/xyz/openbmc_project/network";
static constexpr const char* networkChConfigIntfName =
"xyz.openbmc_project.Channel.ChannelAccess";
static constexpr const char* privilegePropertyString = "MaxPrivilege";
static constexpr const char* dBusPropertiesInterface =
"org.freedesktop.DBus.Properties";
static constexpr const char* propertiesChangedSignal = "PropertiesChanged";
static constexpr const char* interfaceAddedSignal = "InterfacesAdded";
static constexpr const char* interfaceRemovedSignal = "InterfacesRemoved";
// STRING DEFINES: Should sync with key's in JSON
static constexpr const char* nameString = "name";
static constexpr const char* isValidString = "is_valid";
static constexpr const char* activeSessionsString = "active_sessions";
static constexpr const char* maxTransferSizeString = "max_transfer_size";
static constexpr const char* channelInfoString = "channel_info";
static constexpr const char* mediumTypeString = "medium_type";
static constexpr const char* protocolTypeString = "protocol_type";
static constexpr const char* sessionSupportedString = "session_supported";
static constexpr const char* isIpmiString = "is_ipmi";
static constexpr const char* isManagementNIC = "is_management_nic";
static constexpr const char* authTypeSupportedString = "auth_type_supported";
static constexpr const char* accessModeString = "access_mode";
static constexpr const char* userAuthDisabledString = "user_auth_disabled";
static constexpr const char* perMsgAuthDisabledString = "per_msg_auth_disabled";
static constexpr const char* alertingDisabledString = "alerting_disabled";
static constexpr const char* privLimitString = "priv_limit";
static constexpr const char* authTypeEnabledString = "auth_type_enabled";
// Default values
static constexpr const char* defaultChannelName = "RESERVED";
static constexpr const uint8_t defaultMediumType =
static_cast<uint8_t>(EChannelMediumType::reserved);
static constexpr const uint8_t defaultProtocolType =
static_cast<uint8_t>(EChannelProtocolType::reserved);
static constexpr const uint8_t defaultSessionSupported =
static_cast<uint8_t>(EChannelSessSupported::none);
static constexpr const uint8_t defaultAuthType =
static_cast<uint8_t>(EAuthType::none);
static constexpr const bool defaultIsIpmiState = false;
static constexpr size_t smallChannelSize = 64;
std::unique_ptr<sdbusplus::bus::match_t> chPropertiesSignal
__attribute__((init_priority(101)));
std::unique_ptr<sdbusplus::bus::match_t> chInterfaceAddedSignal
__attribute__((init_priority(101)));
std::unique_ptr<sdbusplus::bus::match_t> chInterfaceRemovedSignal
__attribute__((init_priority(101)));
// String mappings use in JSON config file
static std::unordered_map<std::string, EChannelMediumType> mediumTypeMap = {
{"reserved", EChannelMediumType::reserved},
{"ipmb", EChannelMediumType::ipmb},
{"icmb-v1.0", EChannelMediumType::icmbV10},
{"icmb-v0.9", EChannelMediumType::icmbV09},
{"lan-802.3", EChannelMediumType::lan8032},
{"serial", EChannelMediumType::serial},
{"other-lan", EChannelMediumType::otherLan},
{"pci-smbus", EChannelMediumType::pciSmbus},
{"smbus-v1.0", EChannelMediumType::smbusV11},
{"smbus-v2.0", EChannelMediumType::smbusV20},
{"usb-1x", EChannelMediumType::usbV1x},
{"usb-2x", EChannelMediumType::usbV2x},
{"system-interface", EChannelMediumType::systemInterface},
{"oem", EChannelMediumType::oem},
{"unknown", EChannelMediumType::unknown}};
static std::unordered_map<EInterfaceIndex, std::string> interfaceMap = {
{interfaceKCS, "SMS"},
{interfaceLAN1, "eth0"},
{interfaceUnknown, "unknown"}};
static std::unordered_map<std::string, EChannelProtocolType> protocolTypeMap = {
{"na", EChannelProtocolType::na},
{"ipmb-1.0", EChannelProtocolType::ipmbV10},
{"icmb-2.0", EChannelProtocolType::icmbV11},
{"reserved", EChannelProtocolType::reserved},
{"ipmi-smbus", EChannelProtocolType::ipmiSmbus},
{"kcs", EChannelProtocolType::kcs},
{"smic", EChannelProtocolType::smic},
{"bt-10", EChannelProtocolType::bt10},
{"bt-15", EChannelProtocolType::bt15},
{"tmode", EChannelProtocolType::tMode},
{"oem", EChannelProtocolType::oem}};
static std::array<std::string, 4> accessModeList = {
"disabled", "pre-boot", "always_available", "shared"};
static std::array<std::string, 4> sessionSupportList = {
"session-less", "single-session", "multi-session", "session-based"};
const std::array<std::string, PRIVILEGE_OEM + 1> privList = {
"priv-reserved", "priv-callback", "priv-user",
"priv-operator", "priv-admin", "priv-oem"};
std::string ChannelConfig::getChannelName(const uint8_t chNum)
{
if (!isValidChannel(chNum))
{
log<level::ERR>("Invalid channel number.",
entry("CHANNEL_ID=%d", chNum));
throw std::invalid_argument("Invalid channel number");
}
return channelData[chNum].chName;
}
int ChannelConfig::convertToChannelNumberFromChannelName(
const std::string& chName)
{
for (const auto& it : channelData)
{
if (it.chName == chName)
{
return it.chID;
}
}
log<level::ERR>("Invalid channel name.",
entry("CHANNEL=%s", chName.c_str()));
throw std::invalid_argument("Invalid channel name");
return -1;
}
std::string ChannelConfig::getChannelNameFromPath(const std::string& path)
{
const size_t length = strlen(networkIntfObjectBasePath);
if (((length + 1) >= path.size()) ||
path.compare(0, length, networkIntfObjectBasePath))
{
log<level::ERR>("Invalid object path.", entry("PATH=%s", path.c_str()));
throw std::invalid_argument("Invalid object path");
}
std::string chName(path, length + 1);
return chName;
}
void ChannelConfig::processChAccessPropChange(
const std::string& path, const DbusChObjProperties& chProperties)
{
// Get interface name from path. ex: '/xyz/openbmc_project/network/eth0'
std::string chName;
try
{
chName = getChannelNameFromPath(path);
}
catch (const std::invalid_argument& e)
{
log<level::ERR>("Exception: ", entry("MSG=%s", e.what()));
return;
}
// Get the MaxPrivilege property value from the signal
std::string intfPrivStr;
std::string propName;
for (const auto& prop : chProperties)
{
if (prop.first == privilegePropertyString)
{
propName = privilegePropertyString;
intfPrivStr = std::get<std::string>(prop.second);
break;
}
}
if (propName != privilegePropertyString)
{
log<level::ERR>("Unknown signal caught.");
return;
}
if (intfPrivStr.empty())
{
log<level::ERR>("Invalid privilege string.",
entry("INTF=%s", chName.c_str()));
return;
}
uint8_t intfPriv = 0;
int chNum;
try
{
intfPriv = static_cast<uint8_t>(convertToPrivLimitIndex(intfPrivStr));
chNum = convertToChannelNumberFromChannelName(chName);
}
catch (const std::invalid_argument& e)
{
log<level::ERR>("Exception: ", entry("MSG=%s", e.what()));
return;
}
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
// skip updating the values, if this property change originated from IPMI.
if (signalFlag & (1 << chNum))
{
signalFlag &= ~(1 << chNum);
log<level::DEBUG>("Request originated from IPMI so ignoring signal");
return;
}
// Update both volatile & Non-volatile, if there is mismatch.
// as property change other than IPMI, has to update both volatile &
// non-volatile data.
checkAndReloadVolatileData();
checkAndReloadNVData();
if (channelData[chNum].chAccess.chNonVolatileData.privLimit != intfPriv)
{
// Update NV data
channelData[chNum].chAccess.chNonVolatileData.privLimit = intfPriv;
if (writeChannelPersistData() != 0)
{
log<level::ERR>("Failed to update the persist data file");
return;
}
// Update Volatile data
if (channelData[chNum].chAccess.chVolatileData.privLimit != intfPriv)
{
channelData[chNum].chAccess.chVolatileData.privLimit = intfPriv;
if (writeChannelVolatileData() != 0)
{
log<level::ERR>("Failed to update the volatile data file");
return;
}
}
}
return;
}
ChannelConfig& getChannelConfigObject()
{
static ChannelConfig channelConfig;
return channelConfig;
}
ChannelConfig::~ChannelConfig()
{
if (signalHndlrObjectState)
{
chPropertiesSignal.reset();
chInterfaceAddedSignal.reset();
chInterfaceRemovedSignal.reset();
sigHndlrLock.unlock();
}
}
ChannelConfig::ChannelConfig() : bus(ipmid_get_sd_bus_connection())
{
std::ofstream mutexCleanUpFile;
mutexCleanUpFile.open(ipmiChMutexCleanupLockFile,
std::ofstream::out | std::ofstream::app);
if (!mutexCleanUpFile.good())
{
log<level::DEBUG>("Unable to open mutex cleanup file");
return;
}
mutexCleanUpFile.close();
mutexCleanupLock =
boost::interprocess::file_lock(ipmiChMutexCleanupLockFile);
if (mutexCleanupLock.try_lock())
{
boost::interprocess::named_recursive_mutex::remove(ipmiChannelMutex);
channelMutex =
std::make_unique<boost::interprocess::named_recursive_mutex>(
boost::interprocess::open_or_create, ipmiChannelMutex);
mutexCleanupLock.lock_sharable();
}
else
{
mutexCleanupLock.lock_sharable();
channelMutex =
std::make_unique<boost::interprocess::named_recursive_mutex>(
boost::interprocess::open_or_create, ipmiChannelMutex);
}
initChannelPersistData();
sigHndlrLock = boost::interprocess::file_lock(channelNvDataFilename);
// Register it for single object and single process either netipmid /
// host-ipmid
if (chPropertiesSignal == nullptr && sigHndlrLock.try_lock())
{
log<level::DEBUG>("Registering channel signal handler.");
chPropertiesSignal = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusplus::bus::match::rules::path_namespace(
networkIntfObjectBasePath) +
sdbusplus::bus::match::rules::type::signal() +
sdbusplus::bus::match::rules::member(propertiesChangedSignal) +
sdbusplus::bus::match::rules::interface(
dBusPropertiesInterface) +
sdbusplus::bus::match::rules::argN(0, networkChConfigIntfName),
[&](sdbusplus::message_t& msg) {
DbusChObjProperties props;
std::string iface;
std::string path = msg.get_path();
msg.read(iface, props);
processChAccessPropChange(path, props);
});
signalHndlrObjectState = true;
chInterfaceAddedSignal = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusplus::bus::match::rules::type::signal() +
sdbusplus::bus::match::rules::member(interfaceAddedSignal) +
sdbusplus::bus::match::rules::argNpath(
0, std::string(networkIntfObjectBasePath) + "/"),
[&](sdbusplus::message_t&) { initChannelPersistData(); });
chInterfaceRemovedSignal = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusplus::bus::match::rules::type::signal() +
sdbusplus::bus::match::rules::member(interfaceRemovedSignal) +
sdbusplus::bus::match::rules::argNpath(
0, std::string(networkIntfObjectBasePath) + "/"),
[&](sdbusplus::message_t&) { initChannelPersistData(); });
}
}
bool ChannelConfig::isValidChannel(const uint8_t chNum)
{
if (chNum >= maxIpmiChannels)
{
log<level::DEBUG>("Invalid channel ID - Out of range");
return false;
}
if (channelData[chNum].isChValid == false)
{
log<level::DEBUG>("Channel is not valid");
}
return channelData[chNum].isChValid;
}
EChannelSessSupported
ChannelConfig::getChannelSessionSupport(const uint8_t chNum)
{
EChannelSessSupported chSessSupport =
(EChannelSessSupported)channelData[chNum].chInfo.sessionSupported;
return chSessSupport;
}
bool ChannelConfig::isValidAuthType(const uint8_t chNum,
const EAuthType& authType)
{
if ((authType < EAuthType::md2) || (authType > EAuthType::oem))
{
log<level::DEBUG>("Invalid authentication type");
return false;
}
uint8_t authTypeSupported = channelData[chNum].chInfo.authTypeSupported;
if (!(authTypeSupported & (1 << static_cast<uint8_t>(authType))))
{
log<level::DEBUG>("Authentication type is not supported.");
return false;
}
return true;
}
int ChannelConfig::getChannelActiveSessions(const uint8_t chNum)
{
// TODO: TEMPORARY FIX
// Channels active session count is managed separately
// by monitoring channel session which includes LAN and
// RAKP layer changes. This will be updated, once the
// authentication part is implemented.
return channelData[chNum].activeSessCount;
}
size_t ChannelConfig::getChannelMaxTransferSize(uint8_t chNum)
{
return channelData[chNum].maxTransferSize;
}
Cc ChannelConfig::getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo)
{
if (!isValidChannel(chNum))
{
log<level::DEBUG>("Invalid channel");
return ccInvalidFieldRequest;
}
std::copy_n(reinterpret_cast<uint8_t*>(&channelData[chNum].chInfo),
sizeof(channelData[chNum].chInfo),
reinterpret_cast<uint8_t*>(&chInfo));
return ccSuccess;
}
Cc ChannelConfig::getChannelAccessData(const uint8_t chNum,
ChannelAccess& chAccessData)
{
if (!isValidChannel(chNum))
{
log<level::DEBUG>("Invalid channel");
return ccInvalidFieldRequest;
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
log<level::DEBUG>("Session-less channel doesn't have access data.");
return ccActionNotSupportedForChannel;
}
if (checkAndReloadVolatileData() != 0)
{
return ccUnspecifiedError;
}
std::copy_n(
reinterpret_cast<uint8_t*>(&channelData[chNum].chAccess.chVolatileData),
sizeof(channelData[chNum].chAccess.chVolatileData),
reinterpret_cast<uint8_t*>(&chAccessData));
return ccSuccess;
}
Cc ChannelConfig::setChannelAccessData(const uint8_t chNum,
const ChannelAccess& chAccessData,
const uint8_t setFlag)
{
if (!isValidChannel(chNum))
{
log<level::DEBUG>("Invalid channel");
return ccInvalidFieldRequest;
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
log<level::DEBUG>("Session-less channel doesn't have access data.");
return ccActionNotSupportedForChannel;
}
if ((setFlag & setAccessMode) &&
(!isValidAccessMode(chAccessData.accessMode)))
{
log<level::DEBUG>("Invalid access mode specified");
return ccAccessModeNotSupportedForChannel;
}
if ((setFlag & setPrivLimit) && (!isValidPrivLimit(chAccessData.privLimit)))
{
log<level::DEBUG>("Invalid privilege limit specified");
return ccInvalidFieldRequest;
}
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
if (checkAndReloadVolatileData() != 0)
{
return ccUnspecifiedError;
}
if (setFlag & setAccessMode)
{
channelData[chNum].chAccess.chVolatileData.accessMode =
chAccessData.accessMode;
}
if (setFlag & setUserAuthEnabled)
{
channelData[chNum].chAccess.chVolatileData.userAuthDisabled =
chAccessData.userAuthDisabled;
}
if (setFlag & setMsgAuthEnabled)
{
channelData[chNum].chAccess.chVolatileData.perMsgAuthDisabled =
chAccessData.perMsgAuthDisabled;
}
if (setFlag & setAlertingEnabled)
{
channelData[chNum].chAccess.chVolatileData.alertingDisabled =
chAccessData.alertingDisabled;
}
if (setFlag & setPrivLimit)
{
channelData[chNum].chAccess.chVolatileData.privLimit =
chAccessData.privLimit;
}
// Write Volatile data to file
if (writeChannelVolatileData() != 0)
{
log<level::DEBUG>("Failed to update the channel volatile data");
return ccUnspecifiedError;
}
return ccSuccess;
}
Cc ChannelConfig::getChannelAccessPersistData(const uint8_t chNum,
ChannelAccess& chAccessData)
{
if (!isValidChannel(chNum))
{
log<level::DEBUG>("Invalid channel");
return ccInvalidFieldRequest;
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
log<level::DEBUG>("Session-less channel doesn't have access data.");
return ccActionNotSupportedForChannel;
}
if (checkAndReloadNVData() != 0)
{
return ccUnspecifiedError;
}
std::copy_n(reinterpret_cast<uint8_t*>(
&channelData[chNum].chAccess.chNonVolatileData),
sizeof(channelData[chNum].chAccess.chNonVolatileData),
reinterpret_cast<uint8_t*>(&chAccessData));
return ccSuccess;
}
Cc ChannelConfig::setChannelAccessPersistData(const uint8_t chNum,
const ChannelAccess& chAccessData,
const uint8_t setFlag)
{
if (!isValidChannel(chNum))
{
log<level::DEBUG>("Invalid channel");
return ccInvalidFieldRequest;
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
log<level::DEBUG>("Session-less channel doesn't have access data.");
return ccActionNotSupportedForChannel;
}
if ((setFlag & setAccessMode) &&
(!isValidAccessMode(chAccessData.accessMode)))
{
log<level::DEBUG>("Invalid access mode specified");
return ccAccessModeNotSupportedForChannel;
}
if ((setFlag & setPrivLimit) && (!isValidPrivLimit(chAccessData.privLimit)))
{
log<level::DEBUG>("Invalid privilege limit specified");
return ccInvalidFieldRequest;
}
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
if (checkAndReloadNVData() != 0)
{
return ccUnspecifiedError;
}
if (setFlag & setAccessMode)
{
channelData[chNum].chAccess.chNonVolatileData.accessMode =
chAccessData.accessMode;
}
if (setFlag & setUserAuthEnabled)
{
channelData[chNum].chAccess.chNonVolatileData.userAuthDisabled =
chAccessData.userAuthDisabled;
}
if (setFlag & setMsgAuthEnabled)
{
channelData[chNum].chAccess.chNonVolatileData.perMsgAuthDisabled =
chAccessData.perMsgAuthDisabled;
}
if (setFlag & setAlertingEnabled)
{
channelData[chNum].chAccess.chNonVolatileData.alertingDisabled =
chAccessData.alertingDisabled;
}
if (setFlag & setPrivLimit)
{
// Send Update to network channel config interfaces over dbus
std::string privStr = convertToPrivLimitString(chAccessData.privLimit);
std::string networkIntfObj = std::string(networkIntfObjectBasePath) +
"/" + channelData[chNum].chName;
try
{
if (0 != setDbusProperty(networkIntfServiceName, networkIntfObj,
networkChConfigIntfName,
privilegePropertyString, privStr))
{
log<level::DEBUG>(
"Network interface does not exist",
entry("INTERFACE=%s", channelData[chNum].chName.c_str()));
return ccUnspecifiedError;
}
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>("Exception: Network interface does not exist");
return ccInvalidFieldRequest;
}
signalFlag |= (1 << chNum);
channelData[chNum].chAccess.chNonVolatileData.privLimit =
chAccessData.privLimit;
}
// Write persistent data to file
if (writeChannelPersistData() != 0)
{
log<level::DEBUG>("Failed to update the presist data file");
return ccUnspecifiedError;
}
return ccSuccess;
}
Cc ChannelConfig::getChannelAuthTypeSupported(const uint8_t chNum,
uint8_t& authTypeSupported)
{
if (!isValidChannel(chNum))
{
log<level::DEBUG>("Invalid channel");
return ccInvalidFieldRequest;
}
authTypeSupported = channelData[chNum].chInfo.authTypeSupported;
return ccSuccess;
}
Cc ChannelConfig::getChannelEnabledAuthType(const uint8_t chNum,
const uint8_t priv,
EAuthType& authType)
{
if (!isValidChannel(chNum))
{
log<level::DEBUG>("Invalid channel");
return ccInvalidFieldRequest;
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
log<level::DEBUG>("Sessionless channel doesn't have access data.");
return ccInvalidFieldRequest;
}
if (!isValidPrivLimit(priv))
{
log<level::DEBUG>("Invalid privilege specified.");
return ccInvalidFieldRequest;
}
// TODO: Hardcoded for now. Need to implement.
authType = EAuthType::none;
return ccSuccess;
}
std::time_t ChannelConfig::getUpdatedFileTime(const std::string& fileName)
{
struct stat fileStat;
if (stat(fileName.c_str(), &fileStat) != 0)
{
log<level::DEBUG>("Error in getting last updated time stamp");
return -EIO;
}
return fileStat.st_mtime;
}
EChannelAccessMode
ChannelConfig::convertToAccessModeIndex(const std::string& mode)
{
auto iter = std::find(accessModeList.begin(), accessModeList.end(), mode);
if (iter == accessModeList.end())
{
log<level::ERR>("Invalid access mode.",
entry("MODE_STR=%s", mode.c_str()));
throw std::invalid_argument("Invalid access mode.");
}
return static_cast<EChannelAccessMode>(
std::distance(accessModeList.begin(), iter));
}
std::string ChannelConfig::convertToAccessModeString(const uint8_t value)
{
if (accessModeList.size() <= value)
{
log<level::ERR>("Invalid access mode.", entry("MODE_IDX=%d", value));
throw std::invalid_argument("Invalid access mode.");
}
return accessModeList.at(value);
}
CommandPrivilege
ChannelConfig::convertToPrivLimitIndex(const std::string& value)
{
auto iter = std::find(privList.begin(), privList.end(), value);
if (iter == privList.end())
{
log<level::ERR>("Invalid privilege.",
entry("PRIV_STR=%s", value.c_str()));
throw std::invalid_argument("Invalid privilege.");
}
return static_cast<CommandPrivilege>(std::distance(privList.begin(), iter));
}
std::string ChannelConfig::convertToPrivLimitString(const uint8_t value)
{
if (privList.size() <= value)
{
log<level::ERR>("Invalid privilege.", entry("PRIV_IDX=%d", value));
throw std::invalid_argument("Invalid privilege.");
}
return privList.at(value);
}
EChannelSessSupported
ChannelConfig::convertToSessionSupportIndex(const std::string& value)
{
auto iter = std::find(sessionSupportList.begin(), sessionSupportList.end(),
value);
if (iter == sessionSupportList.end())
{
log<level::ERR>("Invalid session supported.",
entry("SESS_STR=%s", value.c_str()));
throw std::invalid_argument("Invalid session supported.");
}
return static_cast<EChannelSessSupported>(
std::distance(sessionSupportList.begin(), iter));
}
EChannelMediumType
ChannelConfig::convertToMediumTypeIndex(const std::string& value)
{
std::unordered_map<std::string, EChannelMediumType>::iterator it =
mediumTypeMap.find(value);
if (it == mediumTypeMap.end())
{
log<level::ERR>("Invalid medium type.",
entry("MEDIUM_STR=%s", value.c_str()));
throw std::invalid_argument("Invalid medium type.");
}
return static_cast<EChannelMediumType>(it->second);
}
EChannelProtocolType
ChannelConfig::convertToProtocolTypeIndex(const std::string& value)
{
std::unordered_map<std::string, EChannelProtocolType>::iterator it =
protocolTypeMap.find(value);
if (it == protocolTypeMap.end())
{
log<level::ERR>("Invalid protocol type.",
entry("PROTO_STR=%s", value.c_str()));
throw std::invalid_argument("Invalid protocol type.");
}
return static_cast<EChannelProtocolType>(it->second);
}
Json ChannelConfig::readJsonFile(const std::string& configFile)
{
std::ifstream jsonFile(configFile);
if (!jsonFile.good())
{
log<level::INFO>("JSON file not found",
entry("FILE_NAME=%s", configFile.c_str()));
return nullptr;
}
Json data = nullptr;
try
{
data = Json::parse(jsonFile, nullptr, false);
}
catch (const Json::parse_error& e)
{
log<level::DEBUG>("Corrupted channel config.",
entry("MSG=%s", e.what()));
throw std::runtime_error("Corrupted channel config file");
}
return data;
}
int ChannelConfig::writeJsonFile(const std::string& configFile,
const Json& jsonData)
{
const std::string tmpFile = configFile + "_tmp";
int fd = open(tmpFile.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_SYNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd < 0)
{
log<level::ERR>("Error in creating json file",
entry("FILE_NAME = %s", tmpFile.c_str()));
return -EIO;
}
const auto& writeData = jsonData.dump();
if (write(fd, writeData.c_str(), writeData.size()) !=
static_cast<ssize_t>(writeData.size()))
{
close(fd);
log<level::ERR>("Error in writing configuration file",
entry("FILE_NAME = %s", tmpFile.c_str()));
return -EIO;
}
close(fd);
if (std::rename(tmpFile.c_str(), configFile.c_str()) != 0)
{
log<level::ERR>("Error in renaming temporary data file",
entry("FILE_NAME = %s", tmpFile.c_str()));
return -EIO;
}
return 0;
}
void ChannelConfig::setDefaultChannelConfig(const uint8_t chNum,
const std::string& chName)
{
channelData[chNum].chName = chName;
channelData[chNum].chID = chNum;
channelData[chNum].isChValid = false;
channelData[chNum].activeSessCount = 0;
channelData[chNum].isManagementNIC = false;
channelData[chNum].chInfo.mediumType = defaultMediumType;
channelData[chNum].chInfo.protocolType = defaultProtocolType;
channelData[chNum].chInfo.sessionSupported = defaultSessionSupported;
channelData[chNum].chInfo.isIpmi = defaultIsIpmiState;
channelData[chNum].chInfo.authTypeSupported = defaultAuthType;
}
uint8_t ChannelConfig::getManagementNICID()
{
static bool idFound = false;
static uint8_t id = 0;
if (idFound)
{
return id;
}
for (uint8_t chIdx = 0; chIdx < maxIpmiChannels; chIdx++)
{
if (channelData[chIdx].isManagementNIC)
{
id = chIdx;
idFound = true;
break;
}
}
if (!idFound)
{
id = static_cast<uint8_t>(EChannelID::chanLan1);
idFound = true;
}
return id;
}
int ChannelConfig::loadChannelConfig()
{
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
Json data = readJsonFile(channelConfigDefaultFilename);
if (data.empty())
{
log<level::DEBUG>("Error in opening IPMI Channel data file");
return -EIO;
}
channelData.fill(ChannelProperties{});
// Collect the list of NIC interfaces connected to the BMC. Use this
// information to only add IPMI channels that have active NIC interfaces.
struct ifaddrs *ifaddr = nullptr, *ifa = nullptr;
if (int err = getifaddrs(&ifaddr); err < 0)
{
log<level::DEBUG>("Unable to acquire network interfaces");
return -EIO;
}
for (int chNum = 0; chNum < maxIpmiChannels; chNum++)
{
try
{
std::string chKey = std::to_string(chNum);
Json jsonChData = data[chKey].get<Json>();
if (jsonChData.is_null())
{
// If user didn't want to configure specific channel (say
// reserved channel), then load that index with default values.
setDefaultChannelConfig(chNum, defaultChannelName);
continue;
}
Json jsonChInfo = jsonChData[channelInfoString].get<Json>();
if (jsonChInfo.is_null())
{
log<level::ERR>("Invalid/corrupted channel config file");
freeifaddrs(ifaddr);
return -EBADMSG;
}
bool channelFound = true;
// Confirm the LAN channel is present
if (jsonChInfo[mediumTypeString].get<std::string>() == "lan-802.3")
{
channelFound = false;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
if (jsonChData[nameString].get<std::string>() ==
ifa->ifa_name)
{
channelFound = true;
break;
}
}
}
ChannelProperties& chData = channelData[chNum];
chData.chID = chNum;
chData.chName = jsonChData[nameString].get<std::string>();
chData.isChValid = channelFound &&
jsonChData[isValidString].get<bool>();
chData.activeSessCount = jsonChData.value(activeSessionsString, 0);
chData.maxTransferSize = jsonChData.value(maxTransferSizeString,
smallChannelSize);
if (jsonChData.count(isManagementNIC) != 0)
{
chData.isManagementNIC =
jsonChData[isManagementNIC].get<bool>();
}
std::string medTypeStr =
jsonChInfo[mediumTypeString].get<std::string>();
chData.chInfo.mediumType =
static_cast<uint8_t>(convertToMediumTypeIndex(medTypeStr));
std::string protoTypeStr =
jsonChInfo[protocolTypeString].get<std::string>();
chData.chInfo.protocolType =
static_cast<uint8_t>(convertToProtocolTypeIndex(protoTypeStr));
std::string sessStr =
jsonChInfo[sessionSupportedString].get<std::string>();
chData.chInfo.sessionSupported =
static_cast<uint8_t>(convertToSessionSupportIndex(sessStr));
chData.chInfo.isIpmi = jsonChInfo[isIpmiString].get<bool>();
chData.chInfo.authTypeSupported = defaultAuthType;
}
catch (const Json::exception& e)
{
log<level::DEBUG>("Json Exception caught.",
entry("MSG=%s", e.what()));
freeifaddrs(ifaddr);
return -EBADMSG;
}
catch (const std::invalid_argument& e)
{
log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what()));
freeifaddrs(ifaddr);
return -EBADMSG;
}
}
freeifaddrs(ifaddr);
return 0;
}
int ChannelConfig::readChannelVolatileData()
{
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
Json data = readJsonFile(channelVolatileDataFilename);
if (data == nullptr)
{
log<level::DEBUG>("Error in opening IPMI Channel data file");
return -EIO;
}
try
{
// Fill in global structure
for (auto it = data.begin(); it != data.end(); ++it)
{
std::string chKey = it.key();
uint8_t chNum = std::stoi(chKey, nullptr, 10);
if (chNum >= maxIpmiChannels)
{
log<level::DEBUG>(
"Invalid channel access entry in config file");
throw std::out_of_range("Out of range - channel number");
}
Json jsonChData = it.value();
if (!jsonChData.is_null())
{
std::string accModeStr =
jsonChData[accessModeString].get<std::string>();
channelData[chNum].chAccess.chVolatileData.accessMode =
static_cast<uint8_t>(convertToAccessModeIndex(accModeStr));
channelData[chNum].chAccess.chVolatileData.userAuthDisabled =
jsonChData[userAuthDisabledString].get<bool>();
channelData[chNum].chAccess.chVolatileData.perMsgAuthDisabled =
jsonChData[perMsgAuthDisabledString].get<bool>();
channelData[chNum].chAccess.chVolatileData.alertingDisabled =
jsonChData[alertingDisabledString].get<bool>();
std::string privStr =
jsonChData[privLimitString].get<std::string>();
channelData[chNum].chAccess.chVolatileData.privLimit =
static_cast<uint8_t>(convertToPrivLimitIndex(privStr));
}
else
{
log<level::ERR>(
"Invalid/corrupted volatile channel access file",
entry("FILE=%s", channelVolatileDataFilename));
throw std::runtime_error(
"Corrupted volatile channel access file");
}
}
}
catch (const Json::exception& e)
{
log<level::DEBUG>("Json Exception caught.", entry("MSG=%s", e.what()));
throw std::runtime_error("Corrupted volatile channel access file");
}
catch (const std::invalid_argument& e)
{
log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what()));
throw std::runtime_error("Corrupted volatile channel access file");
}
// Update the timestamp
voltFileLastUpdatedTime = getUpdatedFileTime(channelVolatileDataFilename);
return 0;
}
int ChannelConfig::readChannelPersistData()
{
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
Json data = readJsonFile(channelNvDataFilename);
if (data == nullptr)
{
log<level::DEBUG>("Error in opening IPMI Channel data file");
return -EIO;
}
try
{
// Fill in global structure
for (auto it = data.begin(); it != data.end(); ++it)
{
std::string chKey = it.key();
uint8_t chNum = std::stoi(chKey, nullptr, 10);
if (chNum >= maxIpmiChannels)
{
log<level::DEBUG>(
"Invalid channel access entry in config file");
throw std::out_of_range("Out of range - channel number");
}
Json jsonChData = it.value();
if (!jsonChData.is_null())
{
std::string accModeStr =
jsonChData[accessModeString].get<std::string>();
channelData[chNum].chAccess.chNonVolatileData.accessMode =
static_cast<uint8_t>(convertToAccessModeIndex(accModeStr));
channelData[chNum].chAccess.chNonVolatileData.userAuthDisabled =
jsonChData[userAuthDisabledString].get<bool>();
channelData[chNum]
.chAccess.chNonVolatileData.perMsgAuthDisabled =
jsonChData[perMsgAuthDisabledString].get<bool>();
channelData[chNum].chAccess.chNonVolatileData.alertingDisabled =
jsonChData[alertingDisabledString].get<bool>();
std::string privStr =
jsonChData[privLimitString].get<std::string>();
channelData[chNum].chAccess.chNonVolatileData.privLimit =
static_cast<uint8_t>(convertToPrivLimitIndex(privStr));
}
else
{
log<level::ERR>("Invalid/corrupted nv channel access file",
entry("FILE=%s", channelNvDataFilename));
throw std::runtime_error("Corrupted nv channel access file");
}
}
}
catch (const Json::exception& e)
{
log<level::DEBUG>("Json Exception caught.", entry("MSG=%s", e.what()));
throw std::runtime_error("Corrupted nv channel access file");
}
catch (const std::invalid_argument& e)
{
log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what()));
throw std::runtime_error("Corrupted nv channel access file");
}
// Update the timestamp
nvFileLastUpdatedTime = getUpdatedFileTime(channelNvDataFilename);
return 0;
}
int ChannelConfig::writeChannelVolatileData()
{
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
Json outData;
try
{
for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
{
if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
{
Json jsonObj;
std::string chKey = std::to_string(chNum);
std::string accModeStr = convertToAccessModeString(
channelData[chNum].chAccess.chVolatileData.accessMode);
jsonObj[accessModeString] = accModeStr;
jsonObj[userAuthDisabledString] =
channelData[chNum].chAccess.chVolatileData.userAuthDisabled;
jsonObj[perMsgAuthDisabledString] =
channelData[chNum]
.chAccess.chVolatileData.perMsgAuthDisabled;
jsonObj[alertingDisabledString] =
channelData[chNum].chAccess.chVolatileData.alertingDisabled;
std::string privStr = convertToPrivLimitString(
channelData[chNum].chAccess.chVolatileData.privLimit);
jsonObj[privLimitString] = privStr;
outData[chKey] = jsonObj;
}
}
}
catch (const std::invalid_argument& e)
{
log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what()));
return -EINVAL;
}
if (writeJsonFile(channelVolatileDataFilename, outData) != 0)
{
log<level::DEBUG>("Error in write JSON data to file");
return -EIO;
}
// Update the timestamp
voltFileLastUpdatedTime = getUpdatedFileTime(channelVolatileDataFilename);
return 0;
}
int ChannelConfig::writeChannelPersistData()
{
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
Json outData;
try
{
for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
{
if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
{
Json jsonObj;
std::string chKey = std::to_string(chNum);
std::string accModeStr = convertToAccessModeString(
channelData[chNum].chAccess.chNonVolatileData.accessMode);
jsonObj[accessModeString] = accModeStr;
jsonObj[userAuthDisabledString] =
channelData[chNum]
.chAccess.chNonVolatileData.userAuthDisabled;
jsonObj[perMsgAuthDisabledString] =
channelData[chNum]
.chAccess.chNonVolatileData.perMsgAuthDisabled;
jsonObj[alertingDisabledString] =
channelData[chNum]
.chAccess.chNonVolatileData.alertingDisabled;
std::string privStr = convertToPrivLimitString(
channelData[chNum].chAccess.chNonVolatileData.privLimit);
jsonObj[privLimitString] = privStr;
outData[chKey] = jsonObj;
}
}
}
catch (const std::invalid_argument& e)
{
log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what()));
return -EINVAL;
}
if (writeJsonFile(channelNvDataFilename, outData) != 0)
{
log<level::DEBUG>("Error in write JSON data to file");
return -EIO;
}
// Update the timestamp
nvFileLastUpdatedTime = getUpdatedFileTime(channelNvDataFilename);
return 0;
}
int ChannelConfig::checkAndReloadNVData()
{
std::time_t updateTime = getUpdatedFileTime(channelNvDataFilename);
int ret = 0;
if (updateTime != nvFileLastUpdatedTime || updateTime == -EIO)
{
try
{
ret = readChannelPersistData();
}
catch (const std::exception& e)
{
log<level::ERR>("Exception caught in readChannelPersistData.",
entry("MSG=%s", e.what()));
ret = -EIO;
}
}
return ret;
}
int ChannelConfig::checkAndReloadVolatileData()
{
std::time_t updateTime = getUpdatedFileTime(channelVolatileDataFilename);
int ret = 0;
if (updateTime != voltFileLastUpdatedTime || updateTime == -EIO)
{
try
{
ret = readChannelVolatileData();
}
catch (const std::exception& e)
{
log<level::ERR>("Exception caught in readChannelVolatileData.",
entry("MSG=%s", e.what()));
ret = -EIO;
}
}
return ret;
}
int ChannelConfig::setDbusProperty(const std::string& service,
const std::string& objPath,
const std::string& interface,
const std::string& property,
const DbusVariant& value)
{
try
{
auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
"org.freedesktop.DBus.Properties",
"Set");
method.append(interface, property, value);
auto reply = bus.call(method);
}
catch (const sdbusplus::exception_t& e)
{
log<level::DEBUG>("set-property failed",
entry("SERVICE=%s", service.c_str()),
entry("OBJPATH=%s", objPath.c_str()),
entry("INTERFACE=%s", interface.c_str()),
entry("PROP=%s", property.c_str()));
return -EIO;
}
return 0;
}
int ChannelConfig::getDbusProperty(const std::string& service,
const std::string& objPath,
const std::string& interface,
const std::string& property,
DbusVariant& value)
{
try
{
auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
"org.freedesktop.DBus.Properties",
"Get");
method.append(interface, property);
auto reply = bus.call(method);
reply.read(value);
}
catch (const sdbusplus::exception_t& e)
{
log<level::DEBUG>("get-property failed",
entry("SERVICE=%s", service.c_str()),
entry("OBJPATH=%s", objPath.c_str()),
entry("INTERFACE=%s", interface.c_str()),
entry("PROP=%s", property.c_str()));
return -EIO;
}
return 0;
}
int ChannelConfig::syncNetworkChannelConfig()
{
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
bool isUpdated = false;
for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
{
if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
{
std::string intfPrivStr;
uint8_t intfPriv = 0;
try
{
std::string networkIntfObj =
std::string(networkIntfObjectBasePath) + "/" +
channelData[chNum].chName;
DbusVariant variant;
if (0 != getDbusProperty(networkIntfServiceName, networkIntfObj,
networkChConfigIntfName,
privilegePropertyString, variant))
{
log<level::DEBUG>("Network interface does not exist",
entry("INTERFACE=%s",
channelData[chNum].chName.c_str()));
continue;
}
intfPrivStr = std::get<std::string>(variant);
intfPriv =
static_cast<uint8_t>(convertToPrivLimitIndex(intfPrivStr));
}
catch (const std::bad_variant_access& e)
{
log<level::DEBUG>(
"exception: Network interface does not exist");
continue;
}
catch (const sdbusplus::exception_t& e)
{
log<level::DEBUG>(
"exception: Network interface does not exist");
continue;
}
catch (const std::invalid_argument& e)
{
log<level::DEBUG>("exception: Invalid privilege");
continue;
}
if (channelData[chNum].chAccess.chNonVolatileData.privLimit !=
intfPriv)
{
isUpdated = true;
channelData[chNum].chAccess.chNonVolatileData.privLimit =
intfPriv;
channelData[chNum].chAccess.chVolatileData.privLimit = intfPriv;
}
}
}
if (isUpdated)
{
// Write persistent data to file
if (writeChannelPersistData() != 0)
{
log<level::DEBUG>("Failed to update the persistent data file");
return -EIO;
}
// Write Volatile data to file
if (writeChannelVolatileData() != 0)
{
log<level::DEBUG>("Failed to update the channel volatile data");
return -EIO;
}
}
return 0;
}
void ChannelConfig::initChannelPersistData()
{
boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
channelLock{*channelMutex};
/* Always read the channel config */
if (loadChannelConfig() != 0)
{
log<level::ERR>("Failed to read channel config file");
throw std::ios_base::failure("Failed to load channel configuration");
}
/* Populate the channel persist data */
if (readChannelPersistData() != 0)
{
// Copy default NV data to RW location
std::filesystem::copy_file(channelAccessDefaultFilename,
channelNvDataFilename);
// Load the channel access NV data
if (readChannelPersistData() != 0)
{
log<level::ERR>("Failed to read channel access NV data");
throw std::ios_base::failure(
"Failed to read channel access NV configuration");
}
}
// First check the volatile data file
// If not present, load the default values
if (readChannelVolatileData() != 0)
{
// Copy default volatile data to temporary location
// NV file(channelNvDataFilename) must have created by now.
std::filesystem::copy_file(channelNvDataFilename,
channelVolatileDataFilename);
// Load the channel access volatile data
if (readChannelVolatileData() != 0)
{
log<level::ERR>("Failed to read channel access volatile data");
throw std::ios_base::failure(
"Failed to read channel access volatile configuration");
}
}
// Synchronize the channel config(priv) with network channel
// configuration(priv) over dbus
if (syncNetworkChannelConfig() != 0)
{
log<level::ERR>(
"Failed to synchronize data with network channel config over dbus");
throw std::ios_base::failure(
"Failed to synchronize data with network channel config over dbus");
}
log<level::DEBUG>("Successfully completed channel data initialization.");
return;
}
} // namespace ipmi