diff options
-rw-r--r-- | ipmid-new.cpp | 218 | ||||
-rw-r--r-- | user_channel/channel_layer.cpp | 4 | ||||
-rw-r--r-- | user_channel/channel_layer.hpp | 9 | ||||
-rw-r--r-- | user_channel/channel_mgmt.hpp | 12 |
4 files changed, 236 insertions, 7 deletions
diff --git a/ipmid-new.cpp b/ipmid-new.cpp index 2a2d4a6..403d4d1 100644 --- a/ipmid-new.cpp +++ b/ipmid-new.cpp @@ -21,6 +21,7 @@ #include <algorithm> #include <any> +#include <boost/algorithm/string.hpp> #include <dcmihandler.hpp> #include <exception> #include <filesystem> @@ -320,21 +321,215 @@ message::Response::ptr executeIpmiCommand(message::Request::ptr request) return executeIpmiCommandCommon(handlerMap, netFn, request); } +namespace utils +{ +template <typename AssocContainer, typename UnaryPredicate> +void assoc_erase_if(AssocContainer& c, UnaryPredicate p) +{ + typename AssocContainer::iterator next = c.begin(); + typename AssocContainer::iterator last = c.end(); + while ((next = std::find_if(next, last, p)) != last) + { + c.erase(next++); + } +} +} // namespace utils + +namespace +{ +std::unordered_map<std::string, uint8_t> uniqueNameToChannelNumber; + +// sdbusplus::bus::match::rules::arg0namespace() wants the prefix +// to match without any trailing '.' +constexpr const char ipmiDbusChannelMatch[] = + "xyz.openbmc_project.Ipmi.Channel"; +void updateOwners(sdbusplus::asio::connection& conn, const std::string& name) +{ + conn.async_method_call( + [name](const boost::system::error_code ec, + const std::string& nameOwner) { + if (ec) + { + log<level::ERR>("Error getting dbus owner", + entry("INTERFACE=%s", name.c_str())); + return; + } + // start after ipmiDbusChannelPrefix (after the '.') + std::string chName = + name.substr(std::strlen(ipmiDbusChannelMatch) + 1); + try + { + uint8_t channel = getChannelByName(chName); + uniqueNameToChannelNumber[nameOwner] = channel; + log<level::INFO>("New interface mapping", + entry("INTERFACE=%s", name.c_str()), + entry("CHANNEL=%u", channel)); + } + catch (const std::exception& e) + { + log<level::INFO>("Failed interface mapping, no such name", + entry("INTERFACE=%s", name.c_str())); + } + }, + "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner", + name); +} + +void doListNames(boost::asio::io_service& io, sdbusplus::asio::connection& conn) +{ + conn.async_method_call( + [&io, &conn](const boost::system::error_code ec, + std::vector<std::string> busNames) { + if (ec) + { + log<level::ERR>("Error getting dbus names"); + std::exit(EXIT_FAILURE); + return; + } + // Try to make startup consistent + std::sort(busNames.begin(), busNames.end()); + + const std::string channelPrefix = + std::string(ipmiDbusChannelMatch) + "."; + for (const std::string& busName : busNames) + { + if (busName.find(channelPrefix) == 0) + { + updateOwners(conn, busName); + } + } + }, + "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", + "ListNames"); +} + +void nameChangeHandler(sdbusplus::message::message& message) +{ + std::string name; + std::string oldOwner; + std::string newOwner; + + message.read(name, oldOwner, newOwner); + + if (!oldOwner.empty()) + { + if (boost::starts_with(oldOwner, ":")) + { + // Connection removed + auto it = uniqueNameToChannelNumber.find(oldOwner); + if (it != uniqueNameToChannelNumber.end()) + { + uniqueNameToChannelNumber.erase(it); + } + } + } + if (!newOwner.empty()) + { + // start after ipmiDbusChannelMatch (and after the '.') + std::string chName = name.substr(std::strlen(ipmiDbusChannelMatch) + 1); + try + { + uint8_t channel = getChannelByName(chName); + uniqueNameToChannelNumber[newOwner] = channel; + log<level::INFO>("New interface mapping", + entry("INTERFACE=%s", name.c_str()), + entry("CHANNEL=%u", channel)); + } + catch (const std::exception& e) + { + log<level::INFO>("Failed interface mapping, no such name", + entry("INTERFACE=%s", name.c_str())); + } + } +}; + +} // anonymous namespace + +static constexpr const char intraBmcName[] = "INTRABMC"; +uint8_t channelFromMessage(sdbusplus::message::message& msg) +{ + // channel name for ipmitool to resolve to + std::string sender = msg.get_sender(); + auto chIter = uniqueNameToChannelNumber.find(sender); + if (chIter != uniqueNameToChannelNumber.end()) + { + return chIter->second; + } + // FIXME: currently internal connections are ephemeral and hard to pin down + try + { + return getChannelByName(intraBmcName); + } + catch (const std::exception& e) + { + return invalidChannel; + } +} // namespace ipmi + /* called from sdbus async server context */ -auto executionEntry(boost::asio::yield_context yield, NetFn netFn, uint8_t lun, +auto executionEntry(boost::asio::yield_context yield, + sdbusplus::message::message& m, NetFn netFn, uint8_t lun, Cmd cmd, std::vector<uint8_t>& data, std::map<std::string, ipmi::Value>& options) { - auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0, - ipmi::Privilege::Admin, &yield); + const auto dbusResponse = + [netFn, lun, cmd](Cc cc, const std::vector<uint8_t>& data = {}) { + constexpr uint8_t netFnResponse = 0x01; + uint8_t retNetFn = netFn | netFnResponse; + return std::make_tuple(retNetFn, lun, cmd, cc, data); + }; + std::string sender = m.get_sender(); + Privilege privilege = Privilege::None; + uint8_t userId = 0; // undefined user + + // figure out what channel the request came in on + uint8_t channel = channelFromMessage(m); + if (channel == invalidChannel) + { + // unknown sender channel; refuse to service the request + log<level::ERR>("ERROR determining source IPMI channel", + entry("SENDER=%s", sender.c_str()), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd)); + return dbusResponse(ipmi::ccDestinationUnavailable); + } + + // session-based channels are required to provide userId/privilege + if (getChannelSessionSupport(channel) != EChannelSessSupported::none) + { + try + { + Value requestPriv = options.at("privilege"); + Value requestUserId = options.at("userId"); + privilege = static_cast<Privilege>(std::get<int>(requestPriv)); + userId = static_cast<uint8_t>(std::get<int>(requestUserId)); + } + catch (const std::exception& e) + { + log<level::ERR>("ERROR determining IPMI session credentials", + entry("CHANNEL=%u", channel), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd)); + return dbusResponse(ipmi::ccUnspecifiedError); + } + } + else + { + // get max privilege for session-less channels + // For now, there is not a way to configure this, default to Admin + privilege = Privilege::Admin; + } + // check to see if the requested priv/username is valid + log<level::DEBUG>("Set up ipmi context", entry("SENDER=%s", sender.c_str()), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd), + entry("CHANNEL=%u", channel), entry("USERID=%u", userId), + entry("PRIVILEGE=%u", static_cast<uint8_t>(privilege))); + + auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, channel, userId, + privilege, &yield); auto request = std::make_shared<ipmi::message::Request>( ctx, std::forward<std::vector<uint8_t>>(data)); message::Response::ptr response = executeIpmiCommand(request); - // Responses in IPMI require a bit set. So there ya go... - netFn |= 0x01; - return std::make_tuple(netFn, lun, cmd, response->cc, - response->payload.raw); + return dbusResponse(response->cc, response->payload.raw); } /** @struct IpmiProvider @@ -623,6 +818,15 @@ int main(int argc, char* argv[]) handleLegacyIpmiCommand); #endif /* ALLOW_DEPRECATED_API */ + // set up bus name watching to match channels with bus names + sdbusplus::bus::match::match nameOwnerChanged( + *sdbusp, + sdbusplus::bus::match::rules::nameOwnerChanged() + + sdbusplus::bus::match::rules::arg0namespace( + ipmi::ipmiDbusChannelMatch), + ipmi::nameChangeHandler); + ipmi::doListNames(*io, *sdbusp); + // set up boost::asio signal handling std::function<SignalResponse(int)> stopAsioRunLoop = [&io](int signalNumber) { diff --git a/user_channel/channel_layer.cpp b/user_channel/channel_layer.cpp index 34a596d..da9c613 100644 --- a/user_channel/channel_layer.cpp +++ b/user_channel/channel_layer.cpp @@ -142,4 +142,8 @@ std::string getChannelName(const uint8_t chNum) return getChannelConfigObject().getChannelName(chNum); } +uint8_t getChannelByName(const std::string& chName) +{ + return getChannelConfigObject().getChannelByName(chName); +} } // namespace ipmi diff --git a/user_channel/channel_layer.hpp b/user_channel/channel_layer.hpp index 1a8d64c..4308794 100644 --- a/user_channel/channel_layer.hpp +++ b/user_channel/channel_layer.hpp @@ -24,6 +24,7 @@ namespace ipmi static constexpr uint8_t maxIpmiChannels = 16; static constexpr uint8_t currentChNum = 0xE; +static constexpr uint8_t invalidChannel = 0xff; /** * @enum IPMI return codes specific to channel (refer spec se 22.22 response @@ -367,4 +368,12 @@ ipmi_ret_t getChannelEnabledAuthType(const uint8_t chNum, const uint8_t priv, */ std::string getChannelName(const uint8_t chNum); +/** @brief Retrieves the LAN channel number from the IPMI channel name + * + * @param[in] chName - IPMI channel name (i.e. eth0) + * + * @return the LAN channel number + */ +uint8_t getChannelByName(const std::string& chName); + } // namespace ipmi diff --git a/user_channel/channel_mgmt.hpp b/user_channel/channel_mgmt.hpp index 35bb494..baeb8c7 100644 --- a/user_channel/channel_mgmt.hpp +++ b/user_channel/channel_mgmt.hpp @@ -105,6 +105,18 @@ class ChannelConfig */ std::string getChannelName(const uint8_t chNum); + /** @brief function to get channel number from channel name + * + * @param[in] chName - channel name + * + * @return network channel interface number + */ + + uint8_t getChannelByName(const std::string& chName) + { + return convertToChannelNumberFromChannelName(chName); + } + /** @brief determines supported session type of a channel * * @param[in] chNum - channel number |