#include "transporthandler.hpp" #include #include #include #include using phosphor::logging::commit; using phosphor::logging::elog; using phosphor::logging::entry; using phosphor::logging::level; using phosphor::logging::log; using sdbusplus::error::xyz::openbmc_project::common::InternalFailure; using sdbusplus::error::xyz::openbmc_project::common::InvalidArgument; using sdbusplus::server::xyz::openbmc_project::network::EthernetInterface; using sdbusplus::server::xyz::openbmc_project::network::IP; using sdbusplus::server::xyz::openbmc_project::network::Neighbor; namespace cipher { std::vector getCipherList() { std::vector cipherList; std::ifstream jsonFile(cipher::configFile); if (!jsonFile.is_open()) { log("Channel Cipher suites file not found"); elog(); } auto data = Json::parse(jsonFile, nullptr, false); if (data.is_discarded()) { log("Parsing channel cipher suites JSON failed"); elog(); } // Byte 1 is reserved cipherList.push_back(0x00); for (const auto& record : data) { cipherList.push_back(record.value(cipher, 0)); } return cipherList; } } // namespace cipher namespace ipmi { namespace transport { /** @brief Valid address origins for IPv4 */ const std::unordered_set originsV4 = { IP::AddressOrigin::Static, IP::AddressOrigin::DHCP, }; static constexpr uint8_t oemCmdStart = 192; // Checks if the ifname is part of the networkd path // This assumes the path came from the network subtree PATH_ROOT bool ifnameInPath(std::string_view ifname, std::string_view path) { constexpr auto rs = PATH_ROOT.size() + 1; // ROOT + separator const auto is = rs + ifname.size(); // ROOT + sep + ifname return path.size() > rs && path.substr(rs).starts_with(ifname) && (path.size() == is || path[is] == '/'); } std::optional maybeGetChannelParams(sdbusplus::bus_t& bus, uint8_t channel) { auto ifname = getChannelName(channel); if (ifname.empty()) { return std::nullopt; } // Enumerate all VLAN + ETHERNET interfaces auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, "GetSubTree"); req.append(std::string_view(PATH_ROOT), 0, std::vector{INTF_VLAN, INTF_ETHERNET}); auto reply = bus.call(req); ObjectTree objs; reply.read(objs); ChannelParams params; for (const auto& [path, impls] : objs) { if (!ifnameInPath(ifname, path)) { continue; } for (const auto& [service, intfs] : impls) { bool vlan = false; bool ethernet = false; for (const auto& intf : intfs) { if (intf == INTF_VLAN) { vlan = true; } else if (intf == INTF_ETHERNET) { ethernet = true; } } if (params.service.empty() && (vlan || ethernet)) { params.service = service; } if (params.ifPath.empty() && !vlan && ethernet) { params.ifPath = path; } if (params.logicalPath.empty() && vlan) { params.logicalPath = path; } } } // We must have a path for the underlying interface if (params.ifPath.empty()) { return std::nullopt; } // We don't have a VLAN so the logical path is the same if (params.logicalPath.empty()) { params.logicalPath = params.ifPath; } params.id = channel; params.ifname = std::move(ifname); return params; } ChannelParams getChannelParams(sdbusplus::bus_t& bus, uint8_t channel) { auto params = maybeGetChannelParams(bus, channel); if (!params) { log("Failed to get channel params", entry("CHANNEL=%" PRIu8, channel)); elog(); } return std::move(*params); } /** @brief Wraps the phosphor logging method to insert some additional metadata * * @param[in] params - The parameters for the channel * ... */ template auto logWithChannel(const ChannelParams& params, Args&&... args) { return log(std::forward(args)..., entry("CHANNEL=%d", params.id), entry("IFNAME=%s", params.ifname.c_str())); } template auto logWithChannel(const std::optional& params, Args&&... args) { if (params) { return logWithChannel(*params, std::forward(args)...); } return log(std::forward(args)...); } /** @brief Get / Set the Property value from phosphor-networkd EthernetInterface */ template static T getEthProp(sdbusplus::bus_t& bus, const ChannelParams& params, const std::string& prop) { return std::get(getDbusProperty(bus, params.service, params.logicalPath, INTF_ETHERNET, prop)); } template static void setEthProp(sdbusplus::bus_t& bus, const ChannelParams& params, const std::string& prop, const T& t) { return setDbusProperty(bus, params.service, params.logicalPath, INTF_ETHERNET, prop, t); } /** @brief Determines the MAC of the ethernet interface * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @return The configured mac address */ stdplus::EtherAddr getMACProperty(sdbusplus::bus_t& bus, const ChannelParams& params) { auto prop = getDbusProperty(bus, params.service, params.ifPath, INTF_MAC, "MACAddress"); return stdplus::fromStr(std::get(prop)); } /** @brief Sets the system value for MAC address on the given interface * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @param[in] mac - MAC address to apply */ void setMACProperty(sdbusplus::bus_t& bus, const ChannelParams& params, stdplus::EtherAddr mac) { setDbusProperty(bus, params.service, params.ifPath, INTF_MAC, "MACAddress", stdplus::toStr(mac)); } void deleteObjectIfExists(sdbusplus::bus_t& bus, const std::string& service, const std::string& path) { if (path.empty()) { return; } try { auto req = bus.new_method_call(service.c_str(), path.c_str(), ipmi::DELETE_INTERFACE, "Delete"); bus.call_noreply(req); } catch (const sdbusplus::exception_t& e) { if (strcmp(e.name(), "xyz.openbmc_project.Common.Error.InternalFailure") != 0 && strcmp(e.name(), "org.freedesktop.DBus.Error.UnknownObject") != 0) { // We want to rethrow real errors throw; } } } /** @brief Sets the address info configured for the interface * If a previous address path exists then it will be removed * before the new address is added. * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @param[in] address - The address of the new IP * @param[in] prefix - The prefix of the new IP */ template void createIfAddr(sdbusplus::bus_t& bus, const ChannelParams& params, typename AddrFamily::addr address, uint8_t prefix) { auto newreq = bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(), INTF_IP_CREATE, "IP"); std::string protocol = sdbusplus::common::xyz::openbmc_project::network::convertForMessage( AddrFamily::protocol); stdplus::ToStrHandle::addr>> tsh; newreq.append(protocol, tsh(address), prefix, ""); bus.call_noreply(newreq); } /** @brief Trivial helper for getting the IPv4 address from getIfAddrs() * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @return The address and prefix if found */ auto getIfAddr4(sdbusplus::bus_t& bus, const ChannelParams& params) { return getIfAddr(bus, params, 0, originsV4); } /** @brief Reconfigures the IPv4 address info configured for the interface * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @param[in] address - The new address if specified * @param[in] prefix - The new address prefix if specified */ void reconfigureIfAddr4(sdbusplus::bus_t& bus, const ChannelParams& params, std::optional address, std::optional prefix) { auto ifaddr = getIfAddr4(bus, params); if (!ifaddr && !address) { log("Missing address for IPv4 assignment"); elog(); } uint8_t fallbackPrefix = AddrFamily::defaultPrefix; if (ifaddr) { fallbackPrefix = ifaddr->prefix; deleteObjectIfExists(bus, params.service, ifaddr->path); } auto addr = address.value_or(ifaddr->address); if (addr != stdplus::In4Addr{}) { createIfAddr(bus, params, addr, prefix.value_or(fallbackPrefix)); } } template std::optional> findGatewayNeighbor(sdbusplus::bus_t& bus, const ChannelParams& params, ObjectLookupCache& neighbors) { auto gateway = getGatewayProperty(bus, params); if (!gateway) { return std::nullopt; } return findStaticNeighbor(bus, params, *gateway, neighbors); } template std::optional> getGatewayNeighbor(sdbusplus::bus_t& bus, const ChannelParams& params) { ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); return findGatewayNeighbor(bus, params, neighbors); } template void reconfigureGatewayMAC(sdbusplus::bus_t& bus, const ChannelParams& params, stdplus::EtherAddr mac) { auto gateway = getGatewayProperty(bus, params); if (!gateway) { log("Tried to set Gateway MAC without Gateway"); elog(); } ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); auto neighbor = findStaticNeighbor(bus, params, *gateway, neighbors); if (neighbor) { deleteObjectIfExists(bus, params.service, neighbor->path); } createNeighbor(bus, params, *gateway, mac); } /** @brief Deconfigures the IPv6 address info configured for the interface * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @param[in] idx - The address index to operate on */ void deconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params, uint8_t idx) { auto ifaddr = getIfAddr(bus, params, idx, originsV6Static); if (ifaddr) { deleteObjectIfExists(bus, params.service, ifaddr->path); } } /** @brief Reconfigures the IPv6 address info configured for the interface * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @param[in] idx - The address index to operate on * @param[in] address - The new address * @param[in] prefix - The new address prefix */ void reconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params, uint8_t idx, stdplus::In6Addr address, uint8_t prefix) { deconfigureIfAddr6(bus, params, idx); createIfAddr(bus, params, address, prefix); } /** @brief Converts the AddressOrigin into an IPv6Source * * @param[in] origin - The DBus Address Origin to convert * @return The IPv6Source version of the origin */ IPv6Source originToSourceType(IP::AddressOrigin origin) { switch (origin) { case IP::AddressOrigin::Static: return IPv6Source::Static; case IP::AddressOrigin::DHCP: return IPv6Source::DHCP; case IP::AddressOrigin::SLAAC: return IPv6Source::SLAAC; default: { auto originStr = sdbusplus::common::xyz::openbmc_project::network:: convertForMessage(origin); log( "Invalid IP::AddressOrigin conversion to IPv6Source", entry("ORIGIN=%s", originStr.c_str())); elog(); } } } /** @brief Packs the IPMI message response with IPv6 address data * * @param[out] ret - The IPMI response payload to be packed * @param[in] channel - The channel id corresponding to an ethernet interface * @param[in] set - The set selector for determining address index * @param[in] origins - Set of valid origins for address filtering */ void getLanIPv6Address(message::Payload& ret, uint8_t channel, uint8_t set, const std::unordered_set& origins) { auto source = IPv6Source::Static; bool enabled = false; stdplus::In6Addr addr{}; uint8_t prefix{}; auto status = IPv6AddressStatus::Disabled; auto ifaddr = channelCall>(channel, set, origins); if (ifaddr) { source = originToSourceType(ifaddr->origin); enabled = (origins == originsV6Static); addr = ifaddr->address; prefix = ifaddr->prefix; status = IPv6AddressStatus::Active; } ret.pack(set); ret.pack(types::enum_cast(source), uint3_t{}, enabled); ret.pack(stdplus::raw::asView(addr)); ret.pack(prefix); ret.pack(types::enum_cast(status)); } /** @brief Gets the vlan ID configured on the interface * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @return VLAN id or the standard 0 for no VLAN */ uint16_t getVLANProperty(sdbusplus::bus_t& bus, const ChannelParams& params) { // VLAN devices will always have a separate logical object if (params.ifPath == params.logicalPath) { return 0; } auto vlan = std::get(getDbusProperty( bus, params.service, params.logicalPath, INTF_VLAN, "Id")); if ((vlan & VLAN_VALUE_MASK) != vlan) { logWithChannel(params, "networkd returned an invalid vlan", entry("VLAN=%" PRIu32, vlan)); elog(); } return vlan; } /** @brief Deletes all of the possible configuration parameters for a channel * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel */ void deconfigureChannel(sdbusplus::bus_t& bus, ChannelParams& params) { // Delete all objects associated with the interface auto objreq = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, "GetSubTree"); objreq.append(std::string_view(PATH_ROOT), 0, std::vector{DELETE_INTERFACE}); auto objreply = bus.call(objreq); ObjectTree objs; objreply.read(objs); for (const auto& [path, impls] : objs) { if (!ifnameInPath(params.ifname, path)) { continue; } for (const auto& [service, intfs] : impls) { deleteObjectIfExists(bus, service, path); } // Update params to reflect the deletion of vlan if (path == params.logicalPath) { params.logicalPath = params.ifPath; } } // Clear out any settings on the lower physical interface setEthProp(bus, params, "DHCP4", false); setEthProp(bus, params, "DHCP6", false); setEthProp(bus, params, "IPv6AcceptRA", false); } /** @brief Creates a new VLAN on the specified interface * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @param[in] vlan - The id of the new vlan */ void createVLAN(sdbusplus::bus_t& bus, ChannelParams& params, uint16_t vlan) { if (vlan == 0) { return; } auto req = bus.new_method_call(params.service.c_str(), PATH_ROOT.c_str(), INTF_VLAN_CREATE, "VLAN"); req.append(params.ifname, static_cast(vlan)); auto reply = bus.call(req); sdbusplus::message::object_path newPath; reply.read(newPath); params.logicalPath = std::move(newPath); } /** @brief Performs the necessary reconfiguration to change the VLAN * * @param[in] bus - The bus object used for lookups * @param[in] params - The parameters for the channel * @param[in] vlan - The new vlan id to use */ void reconfigureVLAN(sdbusplus::bus_t& bus, ChannelParams& params, uint16_t vlan) { // Unfortunatetly we don't have built-in functions to migrate our interface // customizations to new VLAN interfaces, or have some kind of decoupling. // We therefore must retain all of our old information, setup the new VLAN // configuration, then restore the old info. // Save info from the old logical interface bool dhcp4 = getEthProp(bus, params, "DHCP4"); bool dhcp6 = getEthProp(bus, params, "DHCP6"); bool ra = getEthProp(bus, params, "IPv6AcceptRA"); ObjectLookupCache ips(bus, params, INTF_IP); auto ifaddr4 = findIfAddr(bus, params, 0, originsV4, ips); std::vector> ifaddrs6; for (uint8_t i = 0; i < MAX_IPV6_STATIC_ADDRESSES; ++i) { auto ifaddr6 = findIfAddr(bus, params, i, originsV6Static, ips); if (!ifaddr6) { break; } ifaddrs6.push_back(std::move(*ifaddr6)); } ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); auto neighbor4 = findGatewayNeighbor(bus, params, neighbors); auto neighbor6 = findGatewayNeighbor(bus, params, neighbors); deconfigureChannel(bus, params); createVLAN(bus, params, vlan); // Re-establish the saved settings setEthProp(bus, params, "DHCP4", dhcp4); setEthProp(bus, params, "DHCP6", dhcp6); setEthProp(bus, params, "IPv6AcceptRA", ra); if (ifaddr4) { createIfAddr(bus, params, ifaddr4->address, ifaddr4->prefix); } for (const auto& ifaddr6 : ifaddrs6) { createIfAddr(bus, params, ifaddr6.address, ifaddr6.prefix); } if (neighbor4) { createNeighbor(bus, params, neighbor4->ip, neighbor4->mac); } if (neighbor6) { createNeighbor(bus, params, neighbor6->ip, neighbor6->mac); } } // We need to store this value so it can be returned to the client // It is volatile so safe to store in daemon memory. static std::unordered_map setStatus; // Until we have good support for fixed versions of IPMI tool // we need to return the VLAN id for disabled VLANs. The value is only // used for verification that a disable operation succeeded and will only // be sent if our system indicates that vlans are disabled. static std::unordered_map lastDisabledVlan; /** @brief Gets the set status for the channel if it exists * Otherise populates and returns the default value. * * @param[in] channel - The channel id corresponding to an ethernet interface * @return A reference to the SetStatus for the channel */ SetStatus& getSetStatus(uint8_t channel) { auto it = setStatus.find(channel); if (it != setStatus.end()) { return it->second; } return setStatus[channel] = SetStatus::Complete; } /** @brief Unpacks the trivially copyable type from the message */ template static T unpackT(message::Payload& req) { std::array bytes; if (req.unpack(bytes) != 0) { throw ccReqDataLenInvalid; } return stdplus::raw::copyFrom(bytes); } /** @brief Ensure the message is fully unpacked */ static void unpackFinal(message::Payload& req) { if (!req.fullyUnpacked()) { throw ccReqDataTruncated; } } /** * Define placeholder command handlers for the OEM Extension bytes for the Set * LAN Configuration Parameters and Get LAN Configuration Parameters * commands. Using "weak" linking allows the placeholder setLanOem/getLanOem * functions below to be overridden. * To create handlers for your own proprietary command set: * Create/modify a phosphor-ipmi-host Bitbake append file within your Yocto * recipe * Create C++ file(s) that define IPMI handler functions matching the * function names below (i.e. setLanOem). The default name for the * transport IPMI commands is transporthandler_oem.cpp. * Add: * EXTRA_OEMESON:append = "-Dtransport-oem=enabled" * Create a do_configure:prepend()/do_install:append() method in your * bbappend file to copy the file to the build directory. * Add: * PROJECT_SRC_DIR := "${THISDIR}/${PN}" * # Copy the "strong" functions into the working directory, overriding the * # placeholder functions. * do_configure:prepend(){ * cp -f ${PROJECT_SRC_DIR}/transporthandler_oem.cpp ${S} * } * * # Clean up after complilation has completed * do_install:append(){ * rm -f ${S}/transporthandler_oem.cpp * } * */ /** * Define the placeholder OEM commands as having weak linkage. Create * setLanOem, and getLanOem functions in the transporthandler_oem.cpp * file. The functions defined there must not have the "weak" attribute * applied to them. */ RspType<> setLanOem(uint8_t channel, uint8_t parameter, message::Payload& req) __attribute__((weak)); RspType getLanOem(uint8_t channel, uint8_t parameter, uint8_t set, uint8_t block) __attribute__((weak)); RspType<> setLanOem(uint8_t, uint8_t, message::Payload& req) { req.trailingOk = true; return response(ccParamNotSupported); } RspType getLanOem(uint8_t, uint8_t, uint8_t, uint8_t) { return response(ccParamNotSupported); } /** * @brief is a valid LAN channel. * * This function checks whether the input channel is a valid LAN channel or not. * * @param[in] channel: the channel number. * @return nullopt if the channel is invalid, false if the channel is not a LAN * channel, true if the channel is a LAN channel. **/ std::optional isLanChannel(uint8_t channel) { ChannelInfo chInfo; auto cc = getChannelInfo(channel, chInfo); if (cc != ccSuccess) { return std::nullopt; } return chInfo.mediumType == static_cast(EChannelMediumType::lan8032); } RspType<> setLanInt(Context::ptr ctx, uint4_t channelBits, uint4_t reserved1, uint8_t parameter, message::Payload& req) { const uint8_t channel = convertCurrentChannelNum( static_cast(channelBits), ctx->channel); if (reserved1 || !isValidChannel(channel)) { log("Set Lan - Invalid field in request"); req.trailingOk = true; return responseInvalidFieldRequest(); } if (!isLanChannel(channel).value_or(false)) { log("Set Lan - Not a LAN channel"); return responseInvalidFieldRequest(); } switch (static_cast(parameter)) { case LanParam::SetStatus: { uint2_t flag; uint6_t rsvd; if (req.unpack(flag, rsvd) != 0) { return responseReqDataLenInvalid(); } unpackFinal(req); if (rsvd) { return responseInvalidFieldRequest(); } auto status = static_cast(static_cast(flag)); switch (status) { case SetStatus::Complete: { getSetStatus(channel) = status; return responseSuccess(); } case SetStatus::InProgress: { auto& storedStatus = getSetStatus(channel); if (storedStatus == SetStatus::InProgress) { return response(ccParamSetLocked); } storedStatus = status; return responseSuccess(); } case SetStatus::Commit: if (getSetStatus(channel) != SetStatus::InProgress) { return responseInvalidFieldRequest(); } return responseSuccess(); } return response(ccParamNotSupported); } case LanParam::AuthSupport: { req.trailingOk = true; return response(ccParamReadOnly); } case LanParam::AuthEnables: { req.trailingOk = true; return response(ccParamReadOnly); } case LanParam::IP: { if (channelCall>(channel, "DHCP4")) { return responseCommandNotAvailable(); } auto ip = unpackT(req); unpackFinal(req); channelCall(channel, ip, std::nullopt); return responseSuccess(); } case LanParam::IPSrc: { uint4_t flag; uint4_t rsvd; if (req.unpack(flag, rsvd) != 0) { return responseReqDataLenInvalid(); } unpackFinal(req); if (rsvd) { return responseInvalidFieldRequest(); } switch (static_cast(static_cast(flag))) { case IPSrc::DHCP: // The IPSrc IPMI command is only for IPv4 // management. Modifying IPv6 state is done using // a completely different Set LAN Configuration // subcommand. channelCall>(channel, "DHCP4", true); return responseSuccess(); case IPSrc::Unspecified: case IPSrc::Static: channelCall>(channel, "DHCP4", false); return responseSuccess(); case IPSrc::BIOS: case IPSrc::BMC: return responseInvalidFieldRequest(); } return response(ccParamNotSupported); } case LanParam::MAC: { auto mac = unpackT(req); unpackFinal(req); channelCall(channel, mac); return responseSuccess(); } case LanParam::SubnetMask: { if (channelCall>(channel, "DHCP4")) { return responseCommandNotAvailable(); } auto pfx = stdplus::maskToPfx(unpackT(req)); unpackFinal(req); channelCall(channel, std::nullopt, pfx); return responseSuccess(); } case LanParam::Gateway1: { if (channelCall>(channel, "DHCP4")) { return responseCommandNotAvailable(); } auto gateway = unpackT(req); unpackFinal(req); channelCall>(channel, gateway); return responseSuccess(); } case LanParam::Gateway1MAC: { auto gatewayMAC = unpackT(req); unpackFinal(req); channelCall>(channel, gatewayMAC); return responseSuccess(); } case LanParam::VLANId: { uint12_t vlanData; uint3_t rsvd; bool vlanEnable; if (req.unpack(vlanData, rsvd, vlanEnable) != 0) { return responseReqDataLenInvalid(); } unpackFinal(req); if (rsvd) { return responseInvalidFieldRequest(); } uint16_t vlan = static_cast(vlanData); if (!vlanEnable) { lastDisabledVlan[channel] = vlan; vlan = 0; } channelCall(channel, vlan); return responseSuccess(); } case LanParam::CiphersuiteSupport: case LanParam::CiphersuiteEntries: case LanParam::IPFamilySupport: { req.trailingOk = true; return response(ccParamReadOnly); } case LanParam::IPFamilyEnables: { uint8_t enables; if (req.unpack(enables) != 0) { return responseReqDataLenInvalid(); } unpackFinal(req); switch (static_cast(enables)) { case IPFamilyEnables::DualStack: return responseSuccess(); case IPFamilyEnables::IPv4Only: case IPFamilyEnables::IPv6Only: return response(ccParamNotSupported); } return response(ccParamNotSupported); } case LanParam::IPv6Status: { req.trailingOk = true; return response(ccParamReadOnly); } case LanParam::IPv6StaticAddresses: { uint8_t set; uint7_t rsvd; bool enabled; uint8_t prefix; uint8_t status; if (req.unpack(set, rsvd, enabled) != 0) { return responseReqDataLenInvalid(); } auto ip = unpackT(req); if (req.unpack(prefix, status) != 0) { return responseReqDataLenInvalid(); } unpackFinal(req); if (rsvd) { return responseInvalidFieldRequest(); } if (enabled) { if (prefix < MIN_IPV6_PREFIX_LENGTH || prefix > MAX_IPV6_PREFIX_LENGTH) { return responseParmOutOfRange(); } channelCall(channel, set, ip, prefix); } else { channelCall(channel, set); } return responseSuccess(); } case LanParam::IPv6DynamicAddresses: { req.trailingOk = true; return response(ccParamReadOnly); } case LanParam::IPv6RouterControl: { std::bitset<8> control; constexpr uint8_t reservedRACCBits = 0xfc; if (req.unpack(control) != 0) { return responseReqDataLenInvalid(); } unpackFinal(req); if (std::bitset<8> expected(control & std::bitset<8>(reservedRACCBits)); expected.any()) { return response(ccParamNotSupported); } bool enableRA = control[IPv6RouterControlFlag::Dynamic]; channelCall>(channel, "IPv6AcceptRA", enableRA); channelCall>(channel, "DHCP6", enableRA); return responseSuccess(); } case LanParam::IPv6StaticRouter1IP: { auto gateway = unpackT(req); unpackFinal(req); channelCall>(channel, gateway); return responseSuccess(); } case LanParam::IPv6StaticRouter1MAC: { auto mac = unpackT(req); unpackFinal(req); channelCall>(channel, mac); return responseSuccess(); } case LanParam::IPv6StaticRouter1PrefixLength: { uint8_t prefix; if (req.unpack(prefix) != 0) { return responseReqDataLenInvalid(); } unpackFinal(req); if (prefix != 0) { return responseInvalidFieldRequest(); } return responseSuccess(); } case LanParam::IPv6StaticRouter1PrefixValue: { unpackT(req); unpackFinal(req); // Accept any prefix value since our prefix length has to be 0 return responseSuccess(); } case LanParam::cipherSuitePrivilegeLevels: { uint8_t rsvd; std::array cipherSuitePrivs; if (req.unpack(rsvd, cipherSuitePrivs)) { return responseReqDataLenInvalid(); } unpackFinal(req); if (rsvd) { return responseInvalidFieldRequest(); } uint8_t resp = getCipherConfigObject(csPrivFileName, csPrivDefaultFileName) .setCSPrivilegeLevels(channel, cipherSuitePrivs); if (!resp) { return responseSuccess(); } else { req.trailingOk = true; return response(resp); } } } if (parameter >= oemCmdStart) { return setLanOem(channel, parameter, req); } req.trailingOk = true; return response(ccParamNotSupported); } RspType<> setLan(Context::ptr ctx, uint4_t channelBits, uint4_t reserved1, uint8_t parameter, message::Payload& req) { try { return setLanInt(ctx, channelBits, reserved1, parameter, req); } catch (ipmi::Cc cc) { return response(cc); } catch (const sdbusplus::exception_t& e) { if (std::string_view{InvalidArgument::errName} == e.name()) { return responseInvalidFieldRequest(); } throw; } } RspType getLan(Context::ptr ctx, uint4_t channelBits, uint3_t reserved, bool revOnly, uint8_t parameter, uint8_t set, uint8_t block) { message::Payload ret; constexpr uint8_t current_revision = 0x11; ret.pack(current_revision); if (revOnly) { return responseSuccess(std::move(ret)); } const uint8_t channel = convertCurrentChannelNum( static_cast(channelBits), ctx->channel); if (reserved || !isValidChannel(channel)) { log("Get Lan - Invalid field in request"); return responseInvalidFieldRequest(); } if (!isLanChannel(channel).value_or(false)) { log("Set Lan - Not a LAN channel"); return responseInvalidFieldRequest(); } static std::vector cipherList; static bool listInit = false; if (!listInit) { try { cipherList = cipher::getCipherList(); listInit = true; } catch (const std::exception& e) {} } switch (static_cast(parameter)) { case LanParam::SetStatus: { SetStatus status; try { status = setStatus.at(channel); } catch (const std::out_of_range&) { status = SetStatus::Complete; } ret.pack(types::enum_cast(status), uint6_t{}); return responseSuccess(std::move(ret)); } case LanParam::AuthSupport: { std::bitset<6> support; ret.pack(support, uint2_t{}); return responseSuccess(std::move(ret)); } case LanParam::AuthEnables: { std::bitset<6> enables; ret.pack(enables, uint2_t{}); // Callback ret.pack(enables, uint2_t{}); // User ret.pack(enables, uint2_t{}); // Operator ret.pack(enables, uint2_t{}); // Admin ret.pack(enables, uint2_t{}); // OEM return responseSuccess(std::move(ret)); } case LanParam::IP: { auto ifaddr = channelCall(channel); stdplus::In4Addr addr{}; if (ifaddr) { addr = ifaddr->address; } ret.pack(stdplus::raw::asView(addr)); return responseSuccess(std::move(ret)); } case LanParam::IPSrc: { auto src = channelCall>(channel, "DHCP4") ? IPSrc::DHCP : IPSrc::Static; ret.pack(types::enum_cast(src), uint4_t{}); return responseSuccess(std::move(ret)); } case LanParam::MAC: { auto mac = channelCall(channel); ret.pack(stdplus::raw::asView(mac)); return responseSuccess(std::move(ret)); } case LanParam::SubnetMask: { auto ifaddr = channelCall(channel); uint8_t prefix = AddrFamily::defaultPrefix; if (ifaddr) { prefix = ifaddr->prefix; } auto netmask = stdplus::pfxToMask(prefix); ret.pack(stdplus::raw::asView(netmask)); return responseSuccess(std::move(ret)); } case LanParam::Gateway1: { auto gateway = channelCall>(channel); ret.pack(stdplus::raw::asView( gateway.value_or(stdplus::In4Addr{}))); return responseSuccess(std::move(ret)); } case LanParam::Gateway1MAC: { stdplus::EtherAddr mac{}; auto neighbor = channelCall>(channel); if (neighbor) { mac = neighbor->mac; } ret.pack(stdplus::raw::asView(mac)); return responseSuccess(std::move(ret)); } case LanParam::VLANId: { uint16_t vlan = channelCall(channel); if (vlan != 0) { vlan |= VLAN_ENABLE_FLAG; } else { vlan = lastDisabledVlan[channel]; } ret.pack(vlan); return responseSuccess(std::move(ret)); } case LanParam::CiphersuiteSupport: { if (getChannelSessionSupport(channel) == EChannelSessSupported::none) { return responseInvalidFieldRequest(); } if (!listInit) { return responseUnspecifiedError(); } ret.pack(static_cast(cipherList.size() - 1)); return responseSuccess(std::move(ret)); } case LanParam::CiphersuiteEntries: { if (getChannelSessionSupport(channel) == EChannelSessSupported::none) { return responseInvalidFieldRequest(); } if (!listInit) { return responseUnspecifiedError(); } ret.pack(cipherList); return responseSuccess(std::move(ret)); } case LanParam::IPFamilySupport: { std::bitset<8> support; support[IPFamilySupportFlag::IPv6Only] = 0; support[IPFamilySupportFlag::DualStack] = 1; support[IPFamilySupportFlag::IPv6Alerts] = 1; ret.pack(support); return responseSuccess(std::move(ret)); } case LanParam::IPFamilyEnables: { ret.pack(static_cast(IPFamilyEnables::DualStack)); return responseSuccess(std::move(ret)); } case LanParam::IPv6Status: { ret.pack(MAX_IPV6_STATIC_ADDRESSES); ret.pack(MAX_IPV6_DYNAMIC_ADDRESSES); std::bitset<8> support; support[IPv6StatusFlag::DHCP] = 1; support[IPv6StatusFlag::SLAAC] = 1; ret.pack(support); return responseSuccess(std::move(ret)); } case LanParam::IPv6StaticAddresses: { if (set >= MAX_IPV6_STATIC_ADDRESSES) { return responseParmOutOfRange(); } getLanIPv6Address(ret, channel, set, originsV6Static); return responseSuccess(std::move(ret)); } case LanParam::IPv6DynamicAddresses: { if (set >= MAX_IPV6_DYNAMIC_ADDRESSES) { return responseParmOutOfRange(); } getLanIPv6Address(ret, channel, set, originsV6Dynamic); return responseSuccess(std::move(ret)); } case LanParam::IPv6RouterControl: { std::bitset<8> control; control[IPv6RouterControlFlag::Dynamic] = channelCall>(channel, "IPv6AcceptRA"); control[IPv6RouterControlFlag::Static] = 1; ret.pack(control); return responseSuccess(std::move(ret)); } case LanParam::IPv6StaticRouter1IP: { stdplus::In6Addr gateway{}; if (!channelCall>(channel, "IPv6AcceptRA")) { gateway = channelCall>(channel).value_or( stdplus::In6Addr{}); } ret.pack(stdplus::raw::asView(gateway)); return responseSuccess(std::move(ret)); } case LanParam::IPv6StaticRouter1MAC: { stdplus::EtherAddr mac{}; auto neighbor = channelCall>(channel); if (neighbor) { mac = neighbor->mac; } ret.pack(stdplus::raw::asView(mac)); return responseSuccess(std::move(ret)); } case LanParam::IPv6StaticRouter1PrefixLength: { ret.pack(UINT8_C(0)); return responseSuccess(std::move(ret)); } case LanParam::IPv6StaticRouter1PrefixValue: { ret.pack(stdplus::raw::asView(stdplus::In6Addr{})); return responseSuccess(std::move(ret)); } case LanParam::cipherSuitePrivilegeLevels: { std::array csPrivilegeLevels; uint8_t resp = getCipherConfigObject(csPrivFileName, csPrivDefaultFileName) .getCSPrivilegeLevels(channel, csPrivilegeLevels); if (!resp) { constexpr uint8_t reserved1 = 0x00; ret.pack(reserved1, csPrivilegeLevels); return responseSuccess(std::move(ret)); } else { return response(resp); } } } if (parameter >= oemCmdStart) { return getLanOem(channel, parameter, set, block); } return response(ccParamNotSupported); } constexpr const char* solInterface = "xyz.openbmc_project.Ipmi.SOL"; constexpr const char* solPath = "/xyz/openbmc_project/ipmi/sol/"; constexpr const uint16_t solDefaultPort = 623; RspType<> setSolConfParams(Context::ptr ctx, uint4_t channelBits, uint4_t /*reserved*/, uint8_t parameter, message::Payload& req) { const uint8_t channel = convertCurrentChannelNum( static_cast(channelBits), ctx->channel); if (!isValidChannel(channel)) { log("Set Sol Config - Invalid channel in request"); return responseInvalidFieldRequest(); } std::string solService{}; std::string solPathWitheEthName = solPath + ipmi::getChannelName(channel); if (ipmi::getService(ctx, solInterface, solPathWitheEthName, solService)) { log("Set Sol Config - Invalid solInterface", entry("SERVICE=%s", solService.c_str()), entry("OBJPATH=%s", solPathWitheEthName.c_str()), entry("INTERFACE=%s", solInterface)); return responseInvalidFieldRequest(); } switch (static_cast(parameter)) { case SolConfParam::Progress: { uint8_t progress; if (req.unpack(progress) != 0 || !req.fullyUnpacked()) { return responseReqDataLenInvalid(); } if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "Progress", progress)) { return responseUnspecifiedError(); } break; } case SolConfParam::Enable: { bool enable; uint7_t reserved2; if (req.unpack(enable, reserved2) != 0 || !req.fullyUnpacked()) { return responseReqDataLenInvalid(); } if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "Enable", enable)) { return responseUnspecifiedError(); } break; } case SolConfParam::Authentication: { uint4_t privilegeBits{}; uint2_t reserved2{}; bool forceAuth = false; bool forceEncrypt = false; if (req.unpack(privilegeBits, reserved2, forceAuth, forceEncrypt) != 0 || !req.fullyUnpacked()) { return responseReqDataLenInvalid(); } uint8_t privilege = static_cast(privilegeBits); if (privilege < static_cast(Privilege::User) || privilege > static_cast(Privilege::Oem)) { return ipmi::responseInvalidFieldRequest(); } if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "Privilege", privilege)) { return responseUnspecifiedError(); } if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "ForceEncryption", forceEncrypt)) { return responseUnspecifiedError(); } if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "ForceAuthentication", forceAuth)) { return responseUnspecifiedError(); } break; } case SolConfParam::Accumulate: { uint8_t interval; uint8_t threshold; if (req.unpack(interval, threshold) != 0 || !req.fullyUnpacked()) { return responseReqDataLenInvalid(); } if (threshold == 0) { return responseInvalidFieldRequest(); } if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "AccumulateIntervalMS", interval)) { return responseUnspecifiedError(); } if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "Threshold", threshold)) { return responseUnspecifiedError(); } break; } case SolConfParam::Retry: { uint3_t countBits; uint5_t reserved2; uint8_t interval; if (req.unpack(countBits, reserved2, interval) != 0 || !req.fullyUnpacked()) { return responseReqDataLenInvalid(); } uint8_t count = static_cast(countBits); if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "RetryCount", count)) { return responseUnspecifiedError(); } if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "RetryIntervalMS", interval)) { return responseUnspecifiedError(); } break; } case SolConfParam::Port: { return response(ipmiCCWriteReadParameter); } case SolConfParam::NonVbitrate: case SolConfParam::Vbitrate: case SolConfParam::Channel: default: return response(ipmiCCParamNotSupported); } return responseSuccess(); } RspType getSolConfParams(Context::ptr ctx, uint4_t channelBits, uint3_t /*reserved*/, bool revOnly, uint8_t parameter, uint8_t /*set*/, uint8_t /*block*/) { message::Payload ret; constexpr uint8_t current_revision = 0x11; ret.pack(current_revision); if (revOnly) { return responseSuccess(std::move(ret)); } const uint8_t channel = convertCurrentChannelNum( static_cast(channelBits), ctx->channel); if (!isValidChannel(channel)) { log("Get Sol Config - Invalid channel in request"); return responseInvalidFieldRequest(); } std::string solService{}; std::string solPathWitheEthName = solPath + ipmi::getChannelName(channel); if (ipmi::getService(ctx, solInterface, solPathWitheEthName, solService)) { log("Set Sol Config - Invalid solInterface", entry("SERVICE=%s", solService.c_str()), entry("OBJPATH=%s", solPathWitheEthName.c_str()), entry("INTERFACE=%s", solInterface)); return responseInvalidFieldRequest(); } switch (static_cast(parameter)) { case SolConfParam::Progress: { uint8_t progress; if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "Progress", progress)) { return responseUnspecifiedError(); } ret.pack(progress); return responseSuccess(std::move(ret)); } case SolConfParam::Enable: { bool enable{}; if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "Enable", enable)) { return responseUnspecifiedError(); } ret.pack(enable, uint7_t{}); return responseSuccess(std::move(ret)); } case SolConfParam::Authentication: { // 4bits, cast when pack uint8_t privilege; bool forceAuth = false; bool forceEncrypt = false; if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "Privilege", privilege)) { return responseUnspecifiedError(); } if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "ForceAuthentication", forceAuth)) { return responseUnspecifiedError(); } if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "ForceEncryption", forceEncrypt)) { return responseUnspecifiedError(); } ret.pack(uint4_t{privilege}, uint2_t{}, forceAuth, forceEncrypt); return responseSuccess(std::move(ret)); } case SolConfParam::Accumulate: { uint8_t interval{}, threshold{}; if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "AccumulateIntervalMS", interval)) { return responseUnspecifiedError(); } if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "Threshold", threshold)) { return responseUnspecifiedError(); } ret.pack(interval, threshold); return responseSuccess(std::move(ret)); } case SolConfParam::Retry: { // 3bits, cast when cast uint8_t count{}; uint8_t interval{}; if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "RetryCount", count)) { return responseUnspecifiedError(); } if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName, solInterface, "RetryIntervalMS", interval)) { return responseUnspecifiedError(); } ret.pack(uint3_t{count}, uint5_t{}, interval); return responseSuccess(std::move(ret)); } case SolConfParam::Port: { auto port = solDefaultPort; ret.pack(static_cast(port)); return responseSuccess(std::move(ret)); } case SolConfParam::Channel: { ret.pack(channel); return responseSuccess(std::move(ret)); } case SolConfParam::NonVbitrate: { uint64_t baudRate; uint8_t encodedBitRate = 0; if (ipmi::getDbusProperty( ctx, "xyz.openbmc_project.Console.default", "/xyz/openbmc_project/console/default", "xyz.openbmc_project.Console.UART", "Baud", baudRate)) { return ipmi::responseUnspecifiedError(); } switch (baudRate) { case 9600: encodedBitRate = 0x06; break; case 19200: encodedBitRate = 0x07; break; case 38400: encodedBitRate = 0x08; break; case 57600: encodedBitRate = 0x09; break; case 115200: encodedBitRate = 0x0a; break; default: break; } ret.pack(encodedBitRate); return responseSuccess(std::move(ret)); } case SolConfParam::Vbitrate: default: return response(ipmiCCParamNotSupported); } return response(ccParamNotSupported); } } // namespace transport } // namespace ipmi void register_netfn_transport_functions() __attribute__((constructor)); void register_netfn_transport_functions() { ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport, ipmi::transport::cmdSetLanConfigParameters, ipmi::Privilege::Admin, ipmi::transport::setLan); ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport, ipmi::transport::cmdGetLanConfigParameters, ipmi::Privilege::Operator, ipmi::transport::getLan); ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport, ipmi::transport::cmdSetSolConfigParameters, ipmi::Privilege::Admin, ipmi::transport::setSolConfParams); ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport, ipmi::transport::cmdGetSolConfigParameters, ipmi::Privilege::User, ipmi::transport::getSolConfParams); }