diff options
Diffstat (limited to 'transporthandler.cpp')
-rw-r--r-- | transporthandler.cpp | 2516 |
1 files changed, 1731 insertions, 785 deletions
diff --git a/transporthandler.cpp b/transporthandler.cpp index 59d933a..a3b3c35 100644 --- a/transporthandler.cpp +++ b/transporthandler.cpp @@ -1,1002 +1,1948 @@ -#include "transporthandler.hpp" - #include "app/channel.hpp" -#include "user_channel/channel_layer.hpp" #include <arpa/inet.h> +#include <netinet/ether.h> -#include <chrono> -#include <filesystem> +#include <array> +#include <bitset> +#include <cinttypes> +#include <cstdint> +#include <cstring> #include <fstream> +#include <functional> #include <ipmid/api.hpp> +#include <ipmid/message.hpp> +#include <ipmid/message/types.hpp> +#include <ipmid/types.hpp> #include <ipmid/utils.hpp> +#include <optional> #include <phosphor-logging/elog-errors.hpp> +#include <phosphor-logging/elog.hpp> #include <phosphor-logging/log.hpp> -#include <sdbusplus/message/types.hpp> -#include <sdbusplus/timer.hpp> +#include <sdbusplus/bus.hpp> +#include <sdbusplus/exception.hpp> #include <string> +#include <string_view> +#include <type_traits> +#include <unordered_map> +#include <unordered_set> +#include <user_channel/channel_layer.hpp> +#include <utility> +#include <vector> #include <xyz/openbmc_project/Common/error.hpp> +#include <xyz/openbmc_project/Network/IP/server.hpp> +#include <xyz/openbmc_project/Network/Neighbor/server.hpp> + +using phosphor::logging::commit; +using phosphor::logging::elog; +using phosphor::logging::entry; +using phosphor::logging::level; +using phosphor::logging::log; +using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; +using sdbusplus::xyz::openbmc_project::Network::server::IP; +using sdbusplus::xyz::openbmc_project::Network::server::Neighbor; -#define SYSTEMD_NETWORKD_DBUS 1 +namespace cipher +{ -#ifdef SYSTEMD_NETWORKD_DBUS -#include <mapper.h> -#include <systemd/sd-bus.h> -#endif +std::vector<uint8_t> getCipherList() +{ + std::vector<uint8_t> cipherList; -// timer for network changes -std::unique_ptr<phosphor::Timer> networkTimer = nullptr; + std::ifstream jsonFile(cipher::configFile); + if (!jsonFile.is_open()) + { + log<level::ERR>("Channel Cipher suites file not found"); + elog<InternalFailure>(); + } -const int SIZE_MAC = 18; // xx:xx:xx:xx:xx:xx -constexpr auto ipv4Protocol = "xyz.openbmc_project.Network.IP.Protocol.IPv4"; + auto data = Json::parse(jsonFile, nullptr, false); + if (data.is_discarded()) + { + log<level::ERR>("Parsing channel cipher suites JSON failed"); + elog<InternalFailure>(); + } + + // Byte 1 is reserved + cipherList.push_back(0x00); -std::map<int, std::unique_ptr<struct ChannelConfig_t>> channelConfig; + for (const auto& record : data) + { + cipherList.push_back(record.value(cipher, 0)); + } -using namespace phosphor::logging; -using namespace sdbusplus::xyz::openbmc_project::Common::Error; + return cipherList; +} +} // namespace cipher -namespace fs = std::filesystem; -namespace variant_ns = sdbusplus::message::variant_ns; +namespace ipmi +{ +namespace transport +{ -void register_netfn_transport_functions() __attribute__((constructor)); +// LAN Handler specific response codes +constexpr Cc ccParamNotSupported = 0x80; +constexpr Cc ccParamSetLocked = 0x81; +constexpr Cc ccParamReadOnly = 0x82; + +// VLANs are a 12-bit value +constexpr uint16_t VLAN_VALUE_MASK = 0x0fff; +constexpr uint16_t VLAN_ENABLE_FLAG = 0x8000; + +// Arbitrary v6 Address Limits to prevent too much output in ipmitool +constexpr uint8_t MAX_IPV6_STATIC_ADDRESSES = 15; +constexpr uint8_t MAX_IPV6_DYNAMIC_ADDRESSES = 15; + +// D-Bus Network Daemon definitions +constexpr auto PATH_ROOT = "/xyz/openbmc_project/network"; +constexpr auto PATH_SYSTEMCONFIG = "/xyz/openbmc_project/network/config"; + +constexpr auto INTF_SYSTEMCONFIG = + "xyz.openbmc_project.Network.SystemConfiguration"; +constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface"; +constexpr auto INTF_IP = "xyz.openbmc_project.Network.IP"; +constexpr auto INTF_IP_CREATE = "xyz.openbmc_project.Network.IP.Create"; +constexpr auto INTF_MAC = "xyz.openbmc_project.Network.MACAddress"; +constexpr auto INTF_NEIGHBOR = "xyz.openbmc_project.Network.Neighbor"; +constexpr auto INTF_NEIGHBOR_CREATE_STATIC = + "xyz.openbmc_project.Network.Neighbor.CreateStatic"; +constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN"; +constexpr auto INTF_VLAN_CREATE = "xyz.openbmc_project.Network.VLAN.Create"; + +/** @brief Generic paramters for different address families */ +template <int family> +struct AddrFamily +{ +}; -struct ChannelConfig_t* getChannelConfig(int channel) +/** @brief Parameter specialization for IPv4 */ +template <> +struct AddrFamily<AF_INET> { - auto item = channelConfig.find(channel); - if (item == channelConfig.end()) - { - channelConfig[channel] = std::make_unique<struct ChannelConfig_t>(); - } + using addr = in_addr; + static constexpr auto protocol = IP::Protocol::IPv4; + static constexpr size_t maxStrLen = INET6_ADDRSTRLEN; + static constexpr uint8_t defaultPrefix = 32; + static constexpr char propertyGateway[] = "DefaultGateway"; +}; + +/** @brief Parameter specialization for IPv6 */ +template <> +struct AddrFamily<AF_INET6> +{ + using addr = in6_addr; + static constexpr auto protocol = IP::Protocol::IPv6; + static constexpr size_t maxStrLen = INET6_ADDRSTRLEN; + static constexpr uint8_t defaultPrefix = 128; + static constexpr char propertyGateway[] = "DefaultGateway6"; +}; + +/** @brief Valid address origins for IPv4 */ +const std::unordered_set<IP::AddressOrigin> originsV4 = { + IP::AddressOrigin::Static, + IP::AddressOrigin::DHCP, +}; + +/** @brief Valid address origins for IPv6 */ +const std::unordered_set<IP::AddressOrigin> originsV6Static = { + IP::AddressOrigin::Static}; +const std::unordered_set<IP::AddressOrigin> originsV6Dynamic = { + IP::AddressOrigin::DHCP, + IP::AddressOrigin::SLAAC, +}; + +/** @brief Interface IP Address configuration parameters */ +template <int family> +struct IfAddr +{ + std::string path; + typename AddrFamily<family>::addr address; + IP::AddressOrigin origin; + uint8_t prefix; +}; + +/** @brief Interface Neighbor configuration parameters */ +template <int family> +struct IfNeigh +{ + std::string path; + typename AddrFamily<family>::addr ip; + ether_addr mac; +}; - return channelConfig[channel].get(); +/** @brief IPMI LAN Parameters */ +enum class LanParam : uint8_t +{ + SetStatus = 0, + AuthSupport = 1, + AuthEnables = 2, + IP = 3, + IPSrc = 4, + MAC = 5, + SubnetMask = 6, + Gateway1 = 12, + Gateway1MAC = 13, + VLANId = 20, + CiphersuiteSupport = 22, + CiphersuiteEntries = 23, + IPFamilySupport = 50, + IPFamilyEnables = 51, + IPv6Status = 55, + IPv6StaticAddresses = 56, + IPv6DynamicAddresses = 59, + IPv6RouterControl = 64, + IPv6StaticRouter1IP = 65, + IPv6StaticRouter1MAC = 66, + IPv6StaticRouter1PrefixLength = 67, + IPv6StaticRouter1PrefixValue = 68, +}; + +static constexpr uint8_t oemCmdStart = 192; +static constexpr uint8_t oemCmdEnd = 255; + +/** @brief IPMI IP Origin Types */ +enum class IPSrc : uint8_t +{ + Unspecified = 0, + Static = 1, + DHCP = 2, + BIOS = 3, + BMC = 4, +}; + +/** @brief IPMI Set Status */ +enum class SetStatus : uint8_t +{ + Complete = 0, + InProgress = 1, + Commit = 2, +}; + +/** @brief IPMI Family Suport Bits */ +namespace IPFamilySupportFlag +{ +constexpr uint8_t IPv6Only = 0; +constexpr uint8_t DualStack = 1; +constexpr uint8_t IPv6Alerts = 2; +} // namespace IPFamilySupportFlag + +/** @brief IPMI IPFamily Enables Flag */ +enum class IPFamilyEnables : uint8_t +{ + IPv4Only = 0, + IPv6Only = 1, + DualStack = 2, +}; + +/** @brief IPMI IPv6 Dyanmic Status Bits */ +namespace IPv6StatusFlag +{ +constexpr uint8_t DHCP = 0; +constexpr uint8_t SLAAC = 1; +}; // namespace IPv6StatusFlag + +/** @brief IPMI IPv6 Source */ +enum class IPv6Source : uint8_t +{ + Static = 0, + SLAAC = 1, + DHCP = 2, +}; + +/** @brief IPMI IPv6 Address Status */ +enum class IPv6AddressStatus : uint8_t +{ + Active = 0, + Disabled = 1, +}; + +namespace IPv6RouterControlFlag +{ +constexpr uint8_t Static = 0; +constexpr uint8_t Dynamic = 1; +}; // namespace IPv6RouterControlFlag + +/** @brief A trivial helper used to determine if two PODs are equal + * + * @params[in] a - The first object to compare + * @params[in] b - The second object to compare + * @return True if the objects are the same bytewise + */ +template <typename T> +bool equal(const T& a, const T& b) +{ + static_assert(std::is_trivially_copyable_v<T>); + return std::memcmp(&a, &b, sizeof(T)) == 0; } -// Helper Function to get IP Address/NetMask/Gateway/MAC Address from Network -// Manager or Cache based on Set-In-Progress State -ipmi_ret_t getNetworkData(uint8_t lan_param, uint8_t* data, int channel) +/** @brief Copies bytes from an array into a trivially copyable container + * + * @params[out] t - The container receiving the data + * @params[in] bytes - The data to copy + */ +template <size_t N, typename T> +void copyInto(T& t, const std::array<uint8_t, N>& bytes) { - ipmi_ret_t rc = IPMI_CC_OK; - sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); + static_assert(std::is_trivially_copyable_v<T>); + static_assert(N == sizeof(T)); + std::memcpy(&t, bytes.data(), bytes.size()); +} + +/** @brief Gets a generic view of the bytes in the input container + * + * @params[in] t - The data to reference + * @return A string_view referencing the bytes in the container + */ +template <typename T> +std::string_view dataRef(const T& t) +{ + static_assert(std::is_trivially_copyable_v<T>); + return {reinterpret_cast<const char*>(&t), sizeof(T)}; +} - auto ethdevice = ipmi::getChannelName(channel); - // if ethdevice is an empty string they weren't expecting this channel. - if (ethdevice.empty()) +/** @brief The dbus parameters for the interface corresponding to a channel + * This helps reduce the number of mapper lookups we need for each + * query and simplifies finding the VLAN interface if needed. + */ +struct ChannelParams +{ + /** @brief The channel ID */ + int id; + /** @brief channel name for the interface */ + std::string ifname; + /** @brief Name of the service on the bus */ + std::string service; + /** @brief Lower level adapter path that is guaranteed to not be a VLAN */ + std::string ifPath; + /** @brief Logical adapter path used for address assignment */ + std::string logicalPath; +}; + +/** @brief Determines the ethernet interface name corresponding to a channel + * Tries to map a VLAN object first so that the address information + * is accurate. Otherwise it gets the standard ethernet interface. + * + * @param[in] bus - The bus object used for lookups + * @param[in] channel - The channel id corresponding to an ethernet interface + * @return Ethernet interface service and object path if it exists + */ +std::optional<ChannelParams> maybeGetChannelParams(sdbusplus::bus::bus& bus, + uint8_t channel) +{ + auto ifname = getChannelName(channel); + if (ifname.empty()) { - // TODO: return error from getNetworkData() - return IPMI_CC_INVALID_FIELD_REQUEST; + return std::nullopt; } - auto ethIP = ethdevice + "/" + ipmi::network::IP_TYPE; - auto channelConf = getChannelConfig(channel); - try + // Enumerate all VLAN + ETHERNET interfaces + auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, + "GetSubTree"); + req.append(PATH_ROOT, 0, + std::vector<std::string>{INTF_VLAN, INTF_ETHERNET}); + auto reply = bus.call(req); + ObjectTree objs; + reply.read(objs); + + ChannelParams params; + for (const auto& [path, impls] : objs) { - switch (static_cast<LanParam>(lan_param)) + if (path.find(ifname) == path.npos) + { + continue; + } + for (const auto& [service, intfs] : impls) { - case LanParam::IP: + bool vlan = false; + bool ethernet = false; + for (const auto& intf : intfs) { - std::string ipaddress; - if (channelConf->lan_set_in_progress == SET_COMPLETE) + if (intf == INTF_VLAN) { - try - { - auto ipObjectInfo = - ipmi::getIPObject(bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ethIP); - - auto properties = ipmi::getAllDbusProperties( - bus, ipObjectInfo.second, ipObjectInfo.first, - ipmi::network::IP_INTERFACE); - - ipaddress = - variant_ns::get<std::string>(properties["Address"]); - } - // ignore the exception, as it is a valid condition that - // the system is not configured with any IP. - catch (InternalFailure& e) - { - // nothing to do. - } + vlan = true; } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) + else if (intf == INTF_ETHERNET) { - ipaddress = channelConf->ipaddr; + ethernet = true; } - - inet_pton(AF_INET, ipaddress.c_str(), - reinterpret_cast<void*>(data)); } - break; - - case LanParam::IPSRC: + if (params.service.empty() && (vlan || ethernet)) + { + params.service = service; + } + if (params.ifPath.empty() && !vlan && ethernet) + { + params.ifPath = path; + } + if (params.logicalPath.empty() && vlan) { - std::string networkInterfacePath; + params.logicalPath = path; + } + } + } - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - try - { - ipmi::ObjectTree ancestorMap; - // if the system is having ip object,then - // get the IP object. - auto ipObject = ipmi::getDbusObject( - bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ethIP); - - // Get the parent interface of the IP object. - try - { - ipmi::InterfaceList interfaces; - interfaces.emplace_back( - ipmi::network::ETHERNET_INTERFACE); - - ancestorMap = ipmi::getAllAncestors( - bus, ipObject.first, std::move(interfaces)); - } - catch (InternalFailure& e) - { - // if unable to get the parent interface - // then commit the error and return. - log<level::ERR>( - "Unable to get the parent interface", - entry("PATH=%s", ipObject.first.c_str()), - entry("INTERFACE=%s", - ipmi::network::ETHERNET_INTERFACE)); - break; - } - // for an ip object there would be single parent - // interface. - networkInterfacePath = ancestorMap.begin()->first; - } - catch (InternalFailure& e) - { - // if there is no ip configured on the system,then - // get the network interface object. - auto networkInterfaceObject = ipmi::getDbusObject( - bus, ipmi::network::ETHERNET_INTERFACE, - ipmi::network::ROOT, ethdevice); + // 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; + } - networkInterfacePath = networkInterfaceObject.first; - } + params.id = channel; + params.ifname = std::move(ifname); + return std::move(params); +} - auto variant = ipmi::getDbusProperty( - bus, ipmi::network::SERVICE, networkInterfacePath, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled"); +/** @brief A trivial helper around maybeGetChannelParams() that throws an + * exception when it is unable to acquire parameters for the channel. + * + * @param[in] bus - The bus object used for lookups + * @param[in] channel - The channel id corresponding to an ethernet interface + * @return Ethernet interface service and object path + */ +ChannelParams getChannelParams(sdbusplus::bus::bus& bus, uint8_t channel) +{ + auto params = maybeGetChannelParams(bus, channel); + if (!params) + { + log<level::ERR>("Failed to get channel params", + entry("CHANNEL=%" PRIu8, channel)); + elog<InternalFailure>(); + } + return std::move(*params); +} - auto dhcpEnabled = variant_ns::get<bool>(variant); - // As per IPMI spec 2=>DHCP, 1=STATIC - auto ipsrc = dhcpEnabled ? ipmi::network::IPOrigin::DHCP - : ipmi::network::IPOrigin::STATIC; +/** @brief Wraps the phosphor logging method to insert some additional metadata + * + * @param[in] params - The parameters for the channel + * ... + */ +template <auto level, typename... Args> +auto logWithChannel(const ChannelParams& params, Args&&... args) +{ + return log<level>(std::forward<Args>(args)..., + entry("CHANNEL=%d", params.id), + entry("IFNAME=%s", params.ifname.c_str())); +} +template <auto level, typename... Args> +auto logWithChannel(const std::optional<ChannelParams>& params, Args&&... args) +{ + if (params) + { + return logWithChannel<level>(*params, std::forward<Args>(args)...); + } + return log<level>(std::forward<Args>(args)...); +} - std::memcpy(data, &ipsrc, ipmi::network::IPSRC_SIZE_BYTE); - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - std::memcpy(data, &(channelConf->ipsrc), - ipmi::network::IPSRC_SIZE_BYTE); - } - } - break; +/** @brief Trivializes using parameter getter functions by providing a bus + * and channel parameters automatically. + * + * @param[in] channel - The channel id corresponding to an ethernet interface + * ... + */ +template <auto func, typename... Args> +auto channelCall(uint8_t channel, Args&&... args) +{ + sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); + auto params = getChannelParams(bus, channel); + return std::invoke(func, bus, params, std::forward<Args>(args)...); +} - case LanParam::SUBNET: - { - unsigned long mask{}; - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - try - { - auto ipObjectInfo = - ipmi::getIPObject(bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ethIP); - - auto properties = ipmi::getAllDbusProperties( - bus, ipObjectInfo.second, ipObjectInfo.first, - ipmi::network::IP_INTERFACE); - - auto prefix = variant_ns::get<uint8_t>( - properties["PrefixLength"]); - mask = ipmi::network::MASK_32_BIT; - mask = htonl(mask << (ipmi::network::BITS_32 - prefix)); - } - // ignore the exception, as it is a valid condition that - // the system is not configured with any IP. - catch (InternalFailure& e) - { - // nothing to do - } - std::memcpy(data, &mask, - ipmi::network::IPV4_ADDRESS_SIZE_BYTE); - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - inet_pton(AF_INET, channelConf->netmask.c_str(), - reinterpret_cast<void*>(data)); - } - } - break; +/** @brief Determines if the ethernet interface is using DHCP + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @return True if DHCP is enabled, false otherwise + */ +bool getDHCPProperty(sdbusplus::bus::bus& bus, const ChannelParams& params) +{ + return std::get<bool>(getDbusProperty( + bus, params.service, params.logicalPath, INTF_ETHERNET, "DHCPEnabled")); +} - case LanParam::GATEWAY: - { - std::string gateway; +/** @brief Sets the system value for DHCP on the given interface + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @param[in] on - Whether or not to enable DHCP + */ +void setDHCPProperty(sdbusplus::bus::bus& bus, const ChannelParams& params, + bool on) +{ + setDbusProperty(bus, params.service, params.logicalPath, INTF_ETHERNET, + "DHCPEnabled", on); +} - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - try - { - auto systemObject = ipmi::getDbusObject( - bus, ipmi::network::SYSTEMCONFIG_INTERFACE, - ipmi::network::ROOT); +/** @brief Converts a human readable MAC string into MAC bytes + * + * @param[in] mac - The MAC string + * @return MAC in bytes + */ +ether_addr stringToMAC(const char* mac) +{ + const ether_addr* ret = ether_aton(mac); + if (ret == nullptr) + { + log<level::ERR>("Invalid MAC Address", entry("MAC=%s", mac)); + elog<InternalFailure>(); + } + return *ret; +} - auto systemProperties = ipmi::getAllDbusProperties( - bus, systemObject.second, systemObject.first, - ipmi::network::SYSTEMCONFIG_INTERFACE); +/** @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 + */ +ether_addr getMACProperty(sdbusplus::bus::bus& bus, const ChannelParams& params) +{ + auto macStr = std::get<std::string>(getDbusProperty( + bus, params.service, params.ifPath, INTF_MAC, "MACAddress")); + return stringToMAC(macStr.c_str()); +} - gateway = variant_ns::get<std::string>( - systemProperties["DefaultGateway"]); - } - // ignore the exception, as it is a valid condition that - // the system is not configured with any IP. - catch (InternalFailure& e) - { - // nothing to do - } - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - gateway = channelConf->gateway; - } +/** @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::bus& bus, const ChannelParams& params, + const ether_addr& mac) +{ + std::string macStr = ether_ntoa(&mac); + setDbusProperty(bus, params.service, params.ifPath, INTF_MAC, "MACAddress", + macStr); +} - inet_pton(AF_INET, gateway.c_str(), - reinterpret_cast<void*>(data)); - } - break; +/** @brief Turns an IP address string into the network byte order form + * NOTE: This version strictly validates family matches + * + * @param[in] address - The string form of the address + * @return A network byte order address or none if conversion failed + */ +template <int family> +std::optional<typename AddrFamily<family>::addr> + maybeStringToAddr(const char* address) +{ + typename AddrFamily<family>::addr ret; + if (inet_pton(family, address, &ret) == 1) + { + return ret; + } + return std::nullopt; +} - case LanParam::MAC: - { - std::string macAddress; - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - auto macObjectInfo = - ipmi::getDbusObject(bus, ipmi::network::MAC_INTERFACE, - ipmi::network::ROOT, ethdevice); +/** @brief Turns an IP address string into the network byte order form + * NOTE: This version strictly validates family matches + * + * @param[in] address - The string form of the address + * @return A network byte order address + */ +template <int family> +typename AddrFamily<family>::addr stringToAddr(const char* address) +{ + auto ret = maybeStringToAddr<family>(address); + if (!ret) + { + log<level::ERR>("Failed to convert IP Address", + entry("FAMILY=%d", family), + entry("ADDRESS=%s", address)); + elog<InternalFailure>(); + } + return *ret; +} - auto variant = ipmi::getDbusProperty( - bus, macObjectInfo.second, macObjectInfo.first, - ipmi::network::MAC_INTERFACE, "MACAddress"); +/** @brief Turns an IP address in network byte order into a string + * + * @param[in] address - The string form of the address + * @return A network byte order address + */ +template <int family> +std::string addrToString(const typename AddrFamily<family>::addr& address) +{ + std::string ret(AddrFamily<family>::maxStrLen, '\0'); + inet_ntop(family, &address, ret.data(), ret.size()); + ret.resize(strlen(ret.c_str())); + return ret; +} - macAddress = variant_ns::get<std::string>(variant); - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - macAddress = channelConf->macAddress; - } +/** @brief Retrieves the current gateway for the address family on the system + * NOTE: The gateway is currently system wide and not per channel + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @return An address representing the gateway address if it exists + */ +template <int family> +std::optional<typename AddrFamily<family>::addr> + getGatewayProperty(sdbusplus::bus::bus& bus, const ChannelParams& params) +{ + auto gatewayStr = std::get<std::string>(getDbusProperty( + bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG, + AddrFamily<family>::propertyGateway)); + if (gatewayStr.empty()) + { + return std::nullopt; + } + return stringToAddr<family>(gatewayStr.c_str()); +} - sscanf(macAddress.c_str(), ipmi::network::MAC_ADDRESS_FORMAT, - (data), (data + 1), (data + 2), (data + 3), (data + 4), - (data + 5)); - } - break; +/** @brief A lazy lookup mechanism for iterating over object properties stored + * in DBus. This will only perform the object lookup when needed, and + * retains a cache of previous lookups to speed up future iterations. + */ +class ObjectLookupCache +{ + public: + using PropertiesCache = std::unordered_map<std::string, PropertyMap>; + + /** @brief Creates a new ObjectLookupCache for the interface on the bus + * NOTE: The inputs to this object must outlive the object since + * they are only referenced by it. + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @param[in] intf - The interface we are looking up + */ + ObjectLookupCache(sdbusplus::bus::bus& bus, const ChannelParams& params, + const char* intf) : + bus(bus), + params(params), intf(intf), + objs(getAllDbusObjects(bus, params.logicalPath, intf, "")) + { + } - case LanParam::VLAN: - { - uint16_t vlanID{}; - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - try - { - auto ipObjectInfo = ipmi::getIPObject( - bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ipmi::network::IP_TYPE); + class iterator : public ObjectTree::const_iterator + { + public: + using value_type = PropertiesCache::value_type; - vlanID = static_cast<uint16_t>( - ipmi::network::getVLAN(ipObjectInfo.first)); + iterator(ObjectTree::const_iterator it, ObjectLookupCache& container) : + ObjectTree::const_iterator(it), container(container), + ret(container.cache.end()) + { + } + value_type& operator*() + { + ret = container.get(ObjectTree::const_iterator::operator*().first); + return *ret; + } + value_type* operator->() + { + return &operator*(); + } - vlanID = htole16(vlanID); + private: + ObjectLookupCache& container; + PropertiesCache::iterator ret; + }; - if (vlanID) - { - // Enable the 16th bit - vlanID |= htole16(ipmi::network::VLAN_ENABLE_MASK); - } - } - // ignore the exception, as it is a valid condition that - // the system is not configured with any IP. - catch (InternalFailure& e) - { - // nothing to do - } + iterator begin() noexcept + { + return iterator(objs.begin(), *this); + } - std::memcpy(data, &vlanID, ipmi::network::VLAN_SIZE_BYTE); - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - std::memcpy(data, &(channelConf->vlanID), - ipmi::network::VLAN_SIZE_BYTE); - } - } - break; + iterator end() noexcept + { + return iterator(objs.end(), *this); + } - default: - rc = IPMI_CC_PARM_OUT_OF_RANGE; + private: + sdbusplus::bus::bus& bus; + const ChannelParams& params; + const char* const intf; + const ObjectTree objs; + PropertiesCache cache; + + /** @brief Gets a cached copy of the object properties if possible + * Otherwise performs a query on DBus to look them up + * + * @param[in] path - The object path to lookup + * @return An iterator for the specified object path + properties + */ + PropertiesCache::iterator get(const std::string& path) + { + auto it = cache.find(path); + if (it != cache.end()) + { + return it; } + auto properties = getAllDbusProperties(bus, params.service, path, intf); + return cache.insert({path, std::move(properties)}).first; } - catch (InternalFailure& e) +}; + +/** @brief Searches the ip object lookup cache for an address matching + * the input parameters. NOTE: The index lacks stability across address + * changes since the network daemon has no notion of stable indicies. + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @param[in] idx - The index of the desired address on the interface + * @param[in] origins - The allowed origins for the address objects + * @param[in] ips - The object lookup cache holding all of the address info + * @return The address and prefix if it was found + */ +template <int family> +std::optional<IfAddr<family>> + findIfAddr(sdbusplus::bus::bus& bus, const ChannelParams& params, + uint8_t idx, + const std::unordered_set<IP::AddressOrigin>& origins, + ObjectLookupCache& ips) +{ + for (const auto& [path, properties] : ips) { - commit<InternalFailure>(); - rc = IPMI_CC_UNSPECIFIED_ERROR; - return rc; + const auto& addrStr = std::get<std::string>(properties.at("Address")); + auto addr = maybeStringToAddr<family>(addrStr.c_str()); + if (!addr) + { + continue; + } + + IP::AddressOrigin origin = IP::convertAddressOriginFromString( + std::get<std::string>(properties.at("Origin"))); + if (origins.find(origin) == origins.end()) + { + continue; + } + + if (idx > 0) + { + idx--; + continue; + } + + IfAddr<family> ifaddr; + ifaddr.path = path; + ifaddr.address = *addr; + ifaddr.prefix = std::get<uint8_t>(properties.at("PrefixLength")); + ifaddr.origin = origin; + return std::move(ifaddr); } - return rc; + + return std::nullopt; } -namespace cipher +/** @brief Trivial helper around findIfAddr that simplifies calls + * for one off lookups. Don't use this if you intend to do multiple + * lookups at a time. + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @param[in] idx - The index of the desired address on the interface + * @param[in] origins - The allowed origins for the address objects + * @return The address and prefix if it was found + */ +template <int family> +auto getIfAddr(sdbusplus::bus::bus& bus, const ChannelParams& params, + uint8_t idx, + const std::unordered_set<IP::AddressOrigin>& origins) { + ObjectLookupCache ips(bus, params, INTF_IP); + return findIfAddr<family>(bus, params, idx, origins, ips); +} -std::vector<uint8_t> getCipherList() +/** @brief Deletes the dbus object. Ignores empty objects or objects that are + * missing from the bus. + * + * @param[in] bus - The bus object used for lookups + * @param[in] service - The name of the service + * @param[in] path - The path of the object to delete + */ +void deleteObjectIfExists(sdbusplus::bus::bus& bus, const std::string& service, + const std::string& path) { - std::vector<uint8_t> cipherList; - - std::ifstream jsonFile(configFile); - if (!jsonFile.is_open()) + if (path.empty()) { - log<level::ERR>("Channel Cipher suites file not found"); - elog<InternalFailure>(); + return; } - - auto data = Json::parse(jsonFile, nullptr, false); - if (data.is_discarded()) + try { - log<level::ERR>("Parsing channel cipher suites JSON failed"); - elog<InternalFailure>(); + auto req = bus.new_method_call(service.c_str(), path.c_str(), + ipmi::DELETE_INTERFACE, "Delete"); + bus.call_noreply(req); } - - // Byte 1 is reserved - cipherList.push_back(0x00); - - for (const auto& record : data) + catch (const sdbusplus::exception::SdBusError& e) { - cipherList.push_back(record.value(cipher, 0)); + if (strcmp(e.name(), "org.freedesktop.DBus.Error.UnknownObject") != 0) + { + // We want to rethrow real errors + throw; + } } +} - return cipherList; +/** @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 <int family> +void createIfAddr(sdbusplus::bus::bus& bus, const ChannelParams& params, + const typename AddrFamily<family>::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::xyz::openbmc_project::Network::server::convertForMessage( + AddrFamily<family>::protocol); + newreq.append(protocol, addrToString<family>(address), prefix, ""); + bus.call_noreply(newreq); } -} // namespace cipher +/** @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::bus& bus, const ChannelParams& params) +{ + return getIfAddr<AF_INET>(bus, params, 0, originsV4); +} -ipmi_ret_t ipmi_transport_wildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t data_len, - ipmi_context_t context) +/** @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::bus& bus, const ChannelParams& params, + const std::optional<in_addr>& address, + std::optional<uint8_t> prefix) { - // Status code. - ipmi_ret_t rc = IPMI_CC_INVALID; - *data_len = 0; - return rc; + auto ifaddr = getIfAddr4(bus, params); + if (!ifaddr && !address) + { + log<level::ERR>("Missing address for IPv4 assignment"); + elog<InternalFailure>(); + } + uint8_t fallbackPrefix = AddrFamily<AF_INET>::defaultPrefix; + if (ifaddr) + { + fallbackPrefix = ifaddr->prefix; + deleteObjectIfExists(bus, params.service, ifaddr->path); + } + createIfAddr<AF_INET>(bus, params, address.value_or(ifaddr->address), + prefix.value_or(fallbackPrefix)); } -struct set_lan_t +template <int family> +std::optional<IfNeigh<family>> + findStaticNeighbor(sdbusplus::bus::bus& bus, const ChannelParams& params, + const typename AddrFamily<family>::addr& ip, + ObjectLookupCache& neighbors) { - uint8_t channel; - uint8_t parameter; - uint8_t data[8]; // Per IPMI spec, not expecting more than this size -} __attribute__((packed)); + const auto state = + sdbusplus::xyz::openbmc_project::Network::server::convertForMessage( + Neighbor::State::Permanent); + for (const auto& [path, neighbor] : neighbors) + { + const auto& ipStr = std::get<std::string>(neighbor.at("IPAddress")); + auto neighIP = maybeStringToAddr<family>(ipStr.c_str()); + if (!neighIP) + { + continue; + } + if (!equal(*neighIP, ip)) + { + continue; + } + if (state != std::get<std::string>(neighbor.at("State"))) + { + continue; + } + + IfNeigh<family> ret; + ret.path = path; + ret.ip = ip; + const auto& macStr = std::get<std::string>(neighbor.at("MACAddress")); + ret.mac = stringToMAC(macStr.c_str()); + return std::move(ret); + } + + return std::nullopt; +} -ipmi_ret_t checkAndUpdateNetwork(int channel) +template <int family> +void createNeighbor(sdbusplus::bus::bus& bus, const ChannelParams& params, + const typename AddrFamily<family>::addr& address, + const ether_addr& mac) { - auto channelConf = getChannelConfig(channel); - using namespace std::chrono_literals; - // time to wait before applying the network changes. - constexpr auto networkTimeout = 10000000us; // 10 sec + auto newreq = + bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(), + INTF_NEIGHBOR_CREATE_STATIC, "Neighbor"); + std::string macStr = ether_ntoa(&mac); + newreq.append(addrToString<family>(address), macStr); + bus.call_noreply(newreq); +} - // Skip the timer. Expecting more update as we are in SET_IN_PROGRESS - if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) +/** @brief Sets the system wide value for the default gateway + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @param[in] gateway - Gateway address to apply + */ +template <int family> +void setGatewayProperty(sdbusplus::bus::bus& bus, const ChannelParams& params, + const typename AddrFamily<family>::addr& address) +{ + // Save the old gateway MAC address if it exists so we can recreate it + auto gateway = getGatewayProperty<family>(bus, params); + std::optional<IfNeigh<family>> neighbor; + if (gateway) { - return IPMI_CC_OK; + ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); + neighbor = findStaticNeighbor<family>(bus, params, *gateway, neighbors); } - // Start the timer, if it is direct single param update without - // SET_IN_PROGRESS or many params updated through SET_IN_PROGRESS to - // SET_COMPLETE Note: Even for update with SET_IN_PROGRESS, don't apply the - // changes immediately, as ipmitool sends each param individually - // through SET_IN_PROGRESS to SET_COMPLETE. - channelConf->flush = true; - if (!networkTimer) + setDbusProperty(bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG, + AddrFamily<family>::propertyGateway, + addrToString<family>(address)); + + // Restore the gateway MAC if we had one + if (neighbor) { - log<level::ERR>("Network timer is not instantiated"); - return IPMI_CC_UNSPECIFIED_ERROR; + deleteObjectIfExists(bus, params.service, neighbor->path); + createNeighbor<family>(bus, params, address, neighbor->mac); } - // start the timer. - networkTimer->start(networkTimeout); - return IPMI_CC_OK; } -ipmi_ret_t ipmi_transport_set_lan(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t data_len, - ipmi_context_t context) +template <int family> +std::optional<IfNeigh<family>> findGatewayNeighbor(sdbusplus::bus::bus& bus, + const ChannelParams& params, + ObjectLookupCache& neighbors) { - ipmi_ret_t rc = IPMI_CC_OK; - *data_len = 0; + auto gateway = getGatewayProperty<family>(bus, params); + if (!gateway) + { + return std::nullopt; + } - char ipaddr[INET_ADDRSTRLEN]; - char netmask[INET_ADDRSTRLEN]; - char gateway[INET_ADDRSTRLEN]; + return findStaticNeighbor<family>(bus, params, *gateway, neighbors); +} - auto reqptr = reinterpret_cast<const set_lan_t*>(request); - sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); +template <int family> +std::optional<IfNeigh<family>> getGatewayNeighbor(sdbusplus::bus::bus& bus, + const ChannelParams& params) +{ + ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); + return findGatewayNeighbor<family>(bus, params, neighbors); +} - // channel number is the lower nibble - int channel = reqptr->channel & CHANNEL_MASK; - auto ethdevice = ipmi::getChannelName(channel); - if (ethdevice.empty()) +template <int family> +void reconfigureGatewayMAC(sdbusplus::bus::bus& bus, + const ChannelParams& params, const ether_addr& mac) +{ + auto gateway = getGatewayProperty<family>(bus, params); + if (!gateway) { - return IPMI_CC_INVALID_FIELD_REQUEST; + log<level::ERR>("Tried to set Gateway MAC without Gateway"); + elog<InternalFailure>(); } - auto channelConf = getChannelConfig(channel); - switch (static_cast<LanParam>(reqptr->parameter)) + ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); + auto neighbor = + findStaticNeighbor<family>(bus, params, *gateway, neighbors); + if (neighbor) { - case LanParam::IP: - { - std::snprintf(ipaddr, INET_ADDRSTRLEN, - ipmi::network::IP_ADDRESS_FORMAT, reqptr->data[0], - reqptr->data[1], reqptr->data[2], reqptr->data[3]); + deleteObjectIfExists(bus, params.service, neighbor->path); + } - channelConf->ipaddr.assign(ipaddr); - } - break; + createNeighbor<family>(bus, params, *gateway, mac); +} - case LanParam::IPSRC: - { - uint8_t ipsrc{}; - std::memcpy(&ipsrc, reqptr->data, ipmi::network::IPSRC_SIZE_BYTE); - channelConf->ipsrc = static_cast<ipmi::network::IPOrigin>(ipsrc); - } - break; +/** @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::bus& bus, const ChannelParams& params, + uint8_t idx) +{ + auto ifaddr = getIfAddr<AF_INET6>(bus, params, idx, originsV6Static); + if (ifaddr) + { + deleteObjectIfExists(bus, params.service, ifaddr->path); + } +} - case LanParam::MAC: +/** @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::bus& bus, const ChannelParams& params, + uint8_t idx, const in6_addr& address, uint8_t prefix) +{ + deconfigureIfAddr6(bus, params, idx); + createIfAddr<AF_INET6>(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: { - char mac[SIZE_MAC]; + auto originStr = sdbusplus::xyz::openbmc_project::Network::server:: + convertForMessage(origin); + log<level::ERR>( + "Invalid IP::AddressOrigin conversion to IPv6Source", + entry("ORIGIN=%s", originStr.c_str())); + elog<InternalFailure>(); + } + } +} - std::snprintf(mac, SIZE_MAC, ipmi::network::MAC_ADDRESS_FORMAT, - reqptr->data[0], reqptr->data[1], reqptr->data[2], - reqptr->data[3], reqptr->data[4], reqptr->data[5]); +/** @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<IP::AddressOrigin>& origins) +{ + auto source = IPv6Source::Static; + bool enabled = false; + in6_addr addr{}; + uint8_t prefix = AddrFamily<AF_INET6>::defaultPrefix; + auto status = IPv6AddressStatus::Disabled; + + auto ifaddr = channelCall<getIfAddr<AF_INET6>>(channel, set, origins); + if (ifaddr) + { + source = originToSourceType(ifaddr->origin); + enabled = true; + addr = ifaddr->address; + prefix = ifaddr->prefix; + status = IPv6AddressStatus::Active; + } - auto macObjectInfo = - ipmi::getDbusObject(bus, ipmi::network::MAC_INTERFACE, - ipmi::network::ROOT, ethdevice); + ret.pack(set); + ret.pack(static_cast<uint4_t>(source), uint3_t{}, enabled); + ret.pack(std::string_view(reinterpret_cast<char*>(&addr), sizeof(addr))); + ret.pack(prefix); + ret.pack(static_cast<uint8_t>(status)); +} - ipmi::setDbusProperty( - bus, macObjectInfo.second, macObjectInfo.first, - ipmi::network::MAC_INTERFACE, "MACAddress", std::string(mac)); +/** @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::bus& bus, const ChannelParams& params) +{ + // VLAN devices will always have a separate logical object + if (params.ifPath == params.logicalPath) + { + return 0; + } - channelConf->macAddress = mac; - } - break; + auto vlan = std::get<uint32_t>(getDbusProperty( + bus, params.service, params.logicalPath, INTF_VLAN, "Id")); + if ((vlan & VLAN_VALUE_MASK) != vlan) + { + logWithChannel<level::ERR>(params, "networkd returned an invalid vlan", + entry("VLAN=%" PRIu32, vlan)); + elog<InternalFailure>(); + } + return vlan; +} - case LanParam::SUBNET: +/** @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::bus& 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(PATH_ROOT, 0, std::vector<std::string>{DELETE_INTERFACE}); + auto objreply = bus.call(objreq); + ObjectTree objs; + objreply.read(objs); + for (const auto& [path, impls] : objs) + { + if (path.find(params.ifname) == path.npos) { - std::snprintf(netmask, INET_ADDRSTRLEN, - ipmi::network::IP_ADDRESS_FORMAT, reqptr->data[0], - reqptr->data[1], reqptr->data[2], reqptr->data[3]); - channelConf->netmask.assign(netmask); + continue; } - break; - - case LanParam::GATEWAY: + for (const auto& [service, intfs] : impls) { - std::snprintf(gateway, INET_ADDRSTRLEN, - ipmi::network::IP_ADDRESS_FORMAT, reqptr->data[0], - reqptr->data[1], reqptr->data[2], reqptr->data[3]); - channelConf->gateway.assign(gateway); + deleteObjectIfExists(bus, service, path); } - break; - - case LanParam::VLAN: + // Update params to reflect the deletion of vlan + if (path == params.logicalPath) { - uint16_t vlan{}; - std::memcpy(&vlan, reqptr->data, ipmi::network::VLAN_SIZE_BYTE); - // We are not storing the enable bit - // We assume that ipmitool always send enable - // bit as 1. - vlan = le16toh(vlan); - channelConf->vlanID = vlan; + params.logicalPath = params.ifPath; } - break; + } - case LanParam::INPROGRESS: - { - if (reqptr->data[0] == SET_COMPLETE) - { - channelConf->lan_set_in_progress = SET_COMPLETE; + // Clear out any settings on the lower physical interface + setDHCPProperty(bus, params, false); +} - log<level::INFO>( - "Network data from Cache", - entry("PREFIX=%s", channelConf->netmask.c_str()), - entry("ADDRESS=%s", channelConf->ipaddr.c_str()), - entry("GATEWAY=%s", channelConf->gateway.c_str()), - entry("VLAN=%d", channelConf->vlanID)); - } - else if (reqptr->data[0] == SET_IN_PROGRESS) // Set In Progress - { - channelConf->lan_set_in_progress = SET_IN_PROGRESS; - } - } - break; +/** @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::bus& bus, ChannelParams& params, uint16_t vlan) +{ + if (vlan == 0) + { + return; + } - default: + auto req = bus.new_method_call(params.service.c_str(), PATH_ROOT, + INTF_VLAN_CREATE, "VLAN"); + req.append(params.ifname, static_cast<uint32_t>(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::bus& 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 + ObjectLookupCache ips(bus, params, INTF_IP); + auto ifaddr4 = findIfAddr<AF_INET>(bus, params, 0, originsV4, ips); + std::vector<IfAddr<AF_INET6>> ifaddrs6; + for (uint8_t i = 0; i < MAX_IPV6_STATIC_ADDRESSES; ++i) + { + auto ifaddr6 = + findIfAddr<AF_INET6>(bus, params, i, originsV6Static, ips); + if (!ifaddr6) { - rc = IPMI_CC_PARM_NOT_SUPPORTED; - return rc; + break; } + ifaddrs6.push_back(std::move(*ifaddr6)); } - rc = checkAndUpdateNetwork(channel); + auto dhcp = getDHCPProperty(bus, params); + ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); + auto neighbor4 = findGatewayNeighbor<AF_INET>(bus, params, neighbors); + auto neighbor6 = findGatewayNeighbor<AF_INET6>(bus, params, neighbors); + + deconfigureChannel(bus, params); + createVLAN(bus, params, vlan); - return rc; + // Re-establish the saved settings + setDHCPProperty(bus, params, dhcp); + if (ifaddr4) + { + createIfAddr<AF_INET>(bus, params, ifaddr4->address, ifaddr4->prefix); + } + for (const auto& ifaddr6 : ifaddrs6) + { + createIfAddr<AF_INET6>(bus, params, ifaddr6.address, ifaddr6.prefix); + } + if (neighbor4) + { + createNeighbor<AF_INET>(bus, params, neighbor4->ip, neighbor4->mac); + } + if (neighbor6) + { + createNeighbor<AF_INET6>(bus, params, neighbor6->ip, neighbor6->mac); + } } -struct get_lan_t +/** @brief Turns a prefix into a netmask + * + * @param[in] prefix - The prefix length + * @return The netmask + */ +in_addr prefixToNetmask(uint8_t prefix) { - uint8_t rev_channel; - uint8_t parameter; - uint8_t parameter_set; - uint8_t parameter_block; -} __attribute__((packed)); + if (prefix > 32) + { + log<level::ERR>("Invalid prefix", entry("PREFIX=%" PRIu8, prefix)); + elog<InternalFailure>(); + } + if (prefix == 0) + { + // Avoids 32-bit lshift by 32 UB + return {}; + } + return {htobe32(~UINT32_C(0) << (32 - prefix))}; +} -ipmi_ret_t ipmi_transport_get_lan(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t data_len, - ipmi_context_t context) +/** @brief Turns a a netmask into a prefix length + * + * @param[in] netmask - The netmask in byte form + * @return The prefix length + */ +uint8_t netmaskToPrefix(in_addr netmask) { - ipmi_ret_t rc = IPMI_CC_OK; - *data_len = 0; - const uint8_t current_revision = 0x11; // Current rev per IPMI Spec 2.0 - - get_lan_t* reqptr = (get_lan_t*)request; - // channel number is the lower nibble - int channel = reqptr->rev_channel & CHANNEL_MASK; + uint32_t x = be32toh(netmask.s_addr); + if ((~x & (~x + 1)) != 0) + { + char maskStr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &netmask, maskStr, sizeof(maskStr)); + log<level::ERR>("Invalid netmask", entry("NETMASK=%s", maskStr)); + elog<InternalFailure>(); + } + return static_cast<bool>(x) + ? AddrFamily<AF_INET>::defaultPrefix - __builtin_ctz(x) + : 0; +} - if (reqptr->rev_channel & 0x80) // Revision is bit 7 +// 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<uint8_t, SetStatus> 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<uint8_t, uint16_t> 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()) { - // Only current revision was requested - *data_len = sizeof(current_revision); - std::memcpy(response, ¤t_revision, *data_len); - return IPMI_CC_OK; + return it->second; } + return setStatus[channel] = SetStatus::Complete; +} - static std::vector<uint8_t> cipherList; - static auto listInit = false; +/** + * 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_OECONF_append = " --enable-transport-oem=yes" + * Create a do_compile_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_compile_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<message::Payload> getLanOem(uint8_t channel, uint8_t parameter, + uint8_t set, uint8_t block) + __attribute__((weak)); + +RspType<> setLanOem(uint8_t channel, uint8_t parameter, message::Payload& req) +{ + req.trailingOk = true; + return response(ccParamNotSupported); +} - if (!listInit) +RspType<message::Payload> getLanOem(uint8_t channel, uint8_t parameter, + uint8_t set, uint8_t block) +{ + return response(ccParamNotSupported); +} +/** + * @brief is MAC address valid. + * + * This function checks whether the MAC address is valid or not. + * + * @param[in] mac - MAC address. + * @return true if MAC address is valid else retun false. + **/ +bool isValidMACAddress(const ether_addr& mac) +{ + // check if mac address is empty + if (equal(mac, ether_addr{})) { - try - { - cipherList = cipher::getCipherList(); - listInit = true; - } - catch (const std::exception& e) - { - return IPMI_CC_UNSPECIFIED_ERROR; - } + return false; + } + // we accept only unicast MAC addresses and same thing has been checked in + // phosphor-network layer. If the least significant bit of the first octet + // is set to 1, it is multicast MAC else it is unicast MAC address. + if (mac.ether_addr_octet[0] & 1) + { + return false; } + return true; +} - auto ethdevice = ipmi::getChannelName(channel); - if (ethdevice.empty()) +RspType<> setLan(uint4_t channelBits, uint4_t, uint8_t parameter, + message::Payload& req) +{ + auto channel = static_cast<uint8_t>(channelBits); + if (!doesDeviceExist(channel)) { - return IPMI_CC_INVALID_FIELD_REQUEST; + req.trailingOk = true; + return responseInvalidFieldRequest(); } - auto channelConf = getChannelConfig(channel); - LanParam param = static_cast<LanParam>(reqptr->parameter); - switch (param) + switch (static_cast<LanParam>(parameter)) { - case LanParam::INPROGRESS: + case LanParam::SetStatus: { - uint8_t buf[] = {current_revision, - channelConf->lan_set_in_progress}; - *data_len = sizeof(buf); - std::memcpy(response, &buf, *data_len); - break; + uint2_t flag; + uint6_t rsvd; + if (req.unpack(flag, rsvd) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + if (rsvd) + { + return responseInvalidFieldRequest(); + } + auto status = static_cast<SetStatus>(static_cast<uint8_t>(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: + case LanParam::AuthSupport: { - uint8_t buf[] = {current_revision, 0x04}; - *data_len = sizeof(buf); - std::memcpy(response, &buf, *data_len); - break; + req.trailingOk = true; + return response(ccParamReadOnly); } - case LanParam::AUTHENABLES: + case LanParam::AuthEnables: { - uint8_t buf[] = {current_revision, 0x04, 0x04, 0x04, 0x04, 0x04}; - *data_len = sizeof(buf); - std::memcpy(response, &buf, *data_len); - break; + req.trailingOk = true; + return response(ccParamReadOnly); } case LanParam::IP: - case LanParam::SUBNET: - case LanParam::GATEWAY: - case LanParam::MAC: { - uint8_t buf[ipmi::network::MAC_ADDRESS_SIZE_BYTE + 1] = {}; - - *data_len = sizeof(current_revision); - std::memcpy(buf, ¤t_revision, *data_len); - - if (getNetworkData(reqptr->parameter, &buf[1], channel) == - IPMI_CC_OK) + if (channelCall<getDHCPProperty>(channel)) + { + return responseCommandNotAvailable(); + } + in_addr ip; + std::array<uint8_t, sizeof(ip)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) { - if (param == LanParam::MAC) + return responseReqDataLenInvalid(); + } + copyInto(ip, bytes); + channelCall<reconfigureIfAddr4>(channel, ip, std::nullopt); + return responseSuccess(); + } + case LanParam::IPSrc: + { + uint4_t flag; + uint4_t rsvd; + if (req.unpack(flag, rsvd) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + if (rsvd) + { + return responseInvalidFieldRequest(); + } + switch (static_cast<IPSrc>(static_cast<uint8_t>(flag))) + { + case IPSrc::DHCP: { - *data_len = sizeof(buf); + channelCall<setDHCPProperty>(channel, true); + return responseSuccess(); } - else + case IPSrc::Unspecified: + case IPSrc::Static: + case IPSrc::BIOS: + case IPSrc::BMC: { - *data_len = ipmi::network::IPV4_ADDRESS_SIZE_BYTE + 1; + channelCall<setDHCPProperty>(channel, false); + return responseSuccess(); } - std::memcpy(response, &buf, *data_len); } - else + return response(ccParamNotSupported); + } + case LanParam::MAC: + { + ether_addr mac; + std::array<uint8_t, sizeof(mac)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) { - rc = IPMI_CC_UNSPECIFIED_ERROR; + return responseReqDataLenInvalid(); } - break; + copyInto(mac, bytes); + + if (!isValidMACAddress(mac)) + { + return responseInvalidFieldRequest(); + } + channelCall<setMACProperty>(channel, mac); + return responseSuccess(); + } + case LanParam::SubnetMask: + { + if (channelCall<getDHCPProperty>(channel)) + { + return responseCommandNotAvailable(); + } + in_addr netmask; + std::array<uint8_t, sizeof(netmask)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(netmask, bytes); + channelCall<reconfigureIfAddr4>(channel, std::nullopt, + netmaskToPrefix(netmask)); + return responseSuccess(); + } + case LanParam::Gateway1: + { + if (channelCall<getDHCPProperty>(channel)) + { + return responseCommandNotAvailable(); + } + in_addr gateway; + std::array<uint8_t, sizeof(gateway)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(gateway, bytes); + channelCall<setGatewayProperty<AF_INET>>(channel, gateway); + return responseSuccess(); + } + case LanParam::Gateway1MAC: + { + ether_addr gatewayMAC; + std::array<uint8_t, sizeof(gatewayMAC)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(gatewayMAC, bytes); + channelCall<reconfigureGatewayMAC<AF_INET>>(channel, gatewayMAC); + return responseSuccess(); } - case LanParam::VLAN: + case LanParam::VLANId: { - uint8_t buf[ipmi::network::VLAN_SIZE_BYTE + 1] = {}; + uint12_t vlanData = 0; + uint3_t reserved = 0; + bool vlanEnable = 0; - *data_len = sizeof(current_revision); - std::memcpy(buf, ¤t_revision, *data_len); - if (getNetworkData(reqptr->parameter, &buf[1], channel) == - IPMI_CC_OK) + if (req.unpack(vlanData) || req.unpack(reserved) || + req.unpack(vlanEnable) || !req.fullyUnpacked()) { - *data_len = sizeof(buf); - std::memcpy(response, &buf, *data_len); + return responseReqDataLenInvalid(); } - break; + + if (reserved) + { + return responseInvalidFieldRequest(); + } + + uint16_t vlan = static_cast<uint16_t>(vlanData); + + if (!vlanEnable) + { + lastDisabledVlan[channel] = vlan; + vlan = 0; + } + channelCall<reconfigureVLAN>(channel, vlan); + + return responseSuccess(); } - case LanParam::IPSRC: + case LanParam::CiphersuiteSupport: + case LanParam::CiphersuiteEntries: + case LanParam::IPFamilySupport: { - uint8_t buff[ipmi::network::IPSRC_SIZE_BYTE + 1] = {}; - *data_len = sizeof(current_revision); - std::memcpy(buff, ¤t_revision, *data_len); - if (getNetworkData(reqptr->parameter, &buff[1], channel) == - IPMI_CC_OK) + req.trailingOk = true; + return response(ccParamReadOnly); + } + case LanParam::IPFamilyEnables: + { + uint8_t enables; + if (req.unpack(enables) != 0 || !req.fullyUnpacked()) { - *data_len = sizeof(buff); - std::memcpy(response, &buff, *data_len); + return responseReqDataLenInvalid(); } - break; + switch (static_cast<IPFamilyEnables>(enables)) + { + case IPFamilyEnables::DualStack: + return responseSuccess(); + case IPFamilyEnables::IPv4Only: + case IPFamilyEnables::IPv6Only: + return response(ccParamNotSupported); + } + return response(ccParamNotSupported); } - case LanParam::CIPHER_SUITE_COUNT: + case LanParam::IPv6Status: { - *(static_cast<uint8_t*>(response)) = current_revision; - // Byte 1 is reserved byte and does not indicate a cipher suite ID, - // so no of cipher suite entry count is one less than the size of - // the vector - auto count = static_cast<uint8_t>(cipherList.size() - 1); - *(static_cast<uint8_t*>(response) + 1) = count; - *data_len = sizeof(current_revision) + sizeof(count); - break; + req.trailingOk = true; + return response(ccParamReadOnly); } - case LanParam::CIPHER_SUITE_ENTRIES: + case LanParam::IPv6StaticAddresses: { - *(static_cast<uint8_t*>(response)) = current_revision; - // Byte 1 is reserved - std::copy_n(cipherList.data(), cipherList.size(), - static_cast<uint8_t*>(response) + 1); - *data_len = sizeof(current_revision) + - static_cast<uint8_t>(cipherList.size()); - break; + uint8_t set; + uint7_t rsvd; + bool enabled; + in6_addr ip; + std::array<uint8_t, sizeof(ip)> ipbytes; + uint8_t prefix; + uint8_t status; + if (req.unpack(set, rsvd, enabled, ipbytes, prefix, status) != 0 || + !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + if (rsvd) + { + return responseInvalidFieldRequest(); + } + copyInto(ip, ipbytes); + if (enabled) + { + channelCall<reconfigureIfAddr6>(channel, set, ip, prefix); + } + else + { + channelCall<deconfigureIfAddr6>(channel, set); + } + return responseSuccess(); + } + case LanParam::IPv6DynamicAddresses: + { + req.trailingOk = true; + return response(ccParamReadOnly); + } + case LanParam::IPv6RouterControl: + { + std::bitset<8> control; + if (req.unpack(control) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + std::bitset<8> expected; + if (channelCall<getDHCPProperty>(channel)) + { + expected[IPv6RouterControlFlag::Dynamic] = 1; + } + else + { + expected[IPv6RouterControlFlag::Static] = 1; + } + if (expected != control) + { + return responseInvalidFieldRequest(); + } + return responseSuccess(); + } + case LanParam::IPv6StaticRouter1IP: + { + in6_addr gateway; + std::array<uint8_t, sizeof(gateway)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(gateway, bytes); + channelCall<setGatewayProperty<AF_INET6>>(channel, gateway); + return responseSuccess(); + } + case LanParam::IPv6StaticRouter1MAC: + { + ether_addr mac; + std::array<uint8_t, sizeof(mac)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(mac, bytes); + channelCall<reconfigureGatewayMAC<AF_INET6>>(channel, mac); + return responseSuccess(); + } + case LanParam::IPv6StaticRouter1PrefixLength: + { + uint8_t prefix; + if (req.unpack(prefix) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + if (prefix != 0) + { + return responseInvalidFieldRequest(); + } + return responseSuccess(); + } + case LanParam::IPv6StaticRouter1PrefixValue: + { + std::array<uint8_t, sizeof(in6_addr)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + // Accept any prefix value since our prefix length has to be 0 + return responseSuccess(); } - default: - log<level::ERR>("Unsupported parameter", - entry("PARAMETER=0x%x", reqptr->parameter)); - rc = IPMI_CC_PARM_NOT_SUPPORTED; } - return rc; + if ((parameter >= oemCmdStart) && (parameter <= oemCmdEnd)) + { + return setLanOem(channel, parameter, req); + } + + req.trailingOk = true; + return response(ccParamNotSupported); } -void applyChanges(int channel) +RspType<message::Payload> getLan(uint4_t channelBits, uint3_t, bool revOnly, + uint8_t parameter, uint8_t set, uint8_t block) { - std::string ipaddress; - std::string gateway; - uint8_t prefix{}; - uint32_t vlanID{}; - std::string networkInterfacePath; - ipmi::DbusObjectInfo ipObject; - ipmi::DbusObjectInfo systemObject; + message::Payload ret; + constexpr uint8_t current_revision = 0x11; + ret.pack(current_revision); - auto ethdevice = ipmi::getChannelName(channel); - if (ethdevice.empty()) + if (revOnly) { - log<level::ERR>("Unable to get the interface name", - entry("CHANNEL=%d", channel)); - return; + return responseSuccess(std::move(ret)); } - auto ethIp = ethdevice + "/" + ipmi::network::IP_TYPE; - auto channelConf = getChannelConfig(channel); - try + auto channel = static_cast<uint8_t>(channelBits); + if (!doesDeviceExist(channel)) { - sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); + return responseInvalidFieldRequest(); + } - log<level::INFO>("Network data from Cache", - entry("PREFIX=%s", channelConf->netmask.c_str()), - entry("ADDRESS=%s", channelConf->ipaddr.c_str()), - entry("GATEWAY=%s", channelConf->gateway.c_str()), - entry("VLAN=%d", channelConf->vlanID), - entry("IPSRC=%d", channelConf->ipsrc)); - if (channelConf->vlanID != ipmi::network::VLAN_ID_MASK) + static std::vector<uint8_t> cipherList; + static bool listInit = false; + if (!listInit) + { + try { - // get the first twelve bits which is vlan id - // not interested in rest of the bits. - channelConf->vlanID = le32toh(channelConf->vlanID); - vlanID = channelConf->vlanID & ipmi::network::VLAN_ID_MASK; + cipherList = cipher::getCipherList(); + listInit = true; } - - // if the asked ip src is DHCP then not interested in - // any given data except vlan. - if (channelConf->ipsrc != ipmi::network::IPOrigin::DHCP) + catch (const std::exception& e) { - // always get the system object - systemObject = - ipmi::getDbusObject(bus, ipmi::network::SYSTEMCONFIG_INTERFACE, - ipmi::network::ROOT); + } + } - // the below code is to determine the mode of the interface - // as the handling is same, if the system is configured with - // DHCP or user has given all the data. + switch (static_cast<LanParam>(parameter)) + { + case LanParam::SetStatus: + { + SetStatus status; try { - ipmi::ObjectTree ancestorMap; - - ipmi::InterfaceList interfaces{ - ipmi::network::ETHERNET_INTERFACE}; - - // if the system is having ip object,then - // get the IP object. - ipObject = ipmi::getIPObject(bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ethIp); - - // Get the parent interface of the IP object. - try - { - ancestorMap = ipmi::getAllAncestors(bus, ipObject.first, - std::move(interfaces)); - } - catch (InternalFailure& e) - { - // if unable to get the parent interface - // then commit the error and return. - log<level::ERR>("Unable to get the parent interface", - entry("PATH=%s", ipObject.first.c_str()), - entry("INTERFACE=%s", - ipmi::network::ETHERNET_INTERFACE)); - commit<InternalFailure>(); - channelConf->clear(); - return; - } - - networkInterfacePath = ancestorMap.begin()->first; + status = setStatus.at(channel); } - catch (InternalFailure& e) + catch (const std::out_of_range&) { - // TODO Currently IPMI supports single interface,need to handle - // Multiple interface through - // https://github.com/openbmc/openbmc/issues/2138 - - // if there is no ip configured on the system,then - // get the network interface object. - auto networkInterfaceObject = - ipmi::getDbusObject(bus, ipmi::network::ETHERNET_INTERFACE, - ipmi::network::ROOT, ethdevice); - - networkInterfacePath = std::move(networkInterfaceObject.first); + status = SetStatus::Complete; } - - // get the configured mode on the system. - auto enableDHCP = variant_ns::get<bool>(ipmi::getDbusProperty( - bus, ipmi::network::SERVICE, networkInterfacePath, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled")); - - // if ip address source is not given then get the ip source mode - // from the system so that it can be applied later. - if (channelConf->ipsrc == ipmi::network::IPOrigin::UNSPECIFIED) + ret.pack(static_cast<uint2_t>(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<getIfAddr4>(channel); + in_addr addr{}; + if (ifaddr) { - channelConf->ipsrc = (enableDHCP) - ? ipmi::network::IPOrigin::DHCP - : ipmi::network::IPOrigin::STATIC; + addr = ifaddr->address; } - - // check whether user has given all the data - // or the configured system interface is dhcp enabled, - // in both of the cases get the values from the cache. - if ((!channelConf->ipaddr.empty() && - !channelConf->netmask.empty() && - !channelConf->gateway.empty()) || - (enableDHCP)) // configured system interface mode = DHCP + ret.pack(dataRef(addr)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPSrc: + { + auto src = IPSrc::Static; + if (channelCall<getDHCPProperty>(channel)) { - // convert mask into prefix - ipaddress = channelConf->ipaddr; - prefix = ipmi::network::toPrefix(AF_INET, channelConf->netmask); - gateway = channelConf->gateway; + src = IPSrc::DHCP; } - else // asked ip src = static and configured system src = static - // or partially given data. + ret.pack(static_cast<uint4_t>(src), uint4_t{}); + return responseSuccess(std::move(ret)); + } + case LanParam::MAC: + { + ether_addr mac = channelCall<getMACProperty>(channel); + ret.pack(dataRef(mac)); + return responseSuccess(std::move(ret)); + } + case LanParam::SubnetMask: + { + auto ifaddr = channelCall<getIfAddr4>(channel); + uint8_t prefix = AddrFamily<AF_INET>::defaultPrefix; + if (ifaddr) { - // We have partial filled cache so get the remaining - // info from the system. - - // Get the network data from the system as user has - // not given all the data then use the data fetched from the - // system but it is implementation dependent,IPMI spec doesn't - // force it. - - // if system is not having any ip object don't throw error, - try - { - auto properties = ipmi::getAllDbusProperties( - bus, ipObject.second, ipObject.first, - ipmi::network::IP_INTERFACE); - - ipaddress = channelConf->ipaddr.empty() - ? variant_ns::get<std::string>( - properties["Address"]) - : channelConf->ipaddr; - - prefix = channelConf->netmask.empty() - ? variant_ns::get<uint8_t>( - properties["PrefixLength"]) - : ipmi::network::toPrefix( - AF_INET, channelConf->netmask); - } - catch (InternalFailure& e) - { - log<level::INFO>( - "Failed to get IP object which matches", - entry("INTERFACE=%s", ipmi::network::IP_INTERFACE), - entry("MATCH=%s", ethIp.c_str())); - } - - auto systemProperties = ipmi::getAllDbusProperties( - bus, systemObject.second, systemObject.first, - ipmi::network::SYSTEMCONFIG_INTERFACE); - - gateway = channelConf->gateway.empty() - ? variant_ns::get<std::string>( - systemProperties["DefaultGateway"]) - : channelConf->gateway; + prefix = ifaddr->prefix; } + in_addr netmask = prefixToNetmask(prefix); + ret.pack(dataRef(netmask)); + return responseSuccess(std::move(ret)); } - - // Currently network manager doesn't support purging of all the - // ip addresses and the vlan interfaces from the parent interface, - // TODO once the support is there, will make the change here. - // https://github.com/openbmc/openbmc/issues/2141. - - // TODO Currently IPMI supports single interface,need to handle - // Multiple interface through - // https://github.com/openbmc/openbmc/issues/2138 - - // instead of deleting all the vlan interfaces and - // all the ipv4 address,we will call reset method. - // delete all the vlan interfaces - - ipmi::deleteAllDbusObjects(bus, ipmi::network::ROOT, - ipmi::network::VLAN_INTERFACE); - - // set the interface mode to static - auto networkInterfaceObject = - ipmi::getDbusObject(bus, ipmi::network::ETHERNET_INTERFACE, - ipmi::network::ROOT, ethdevice); - - // setting the physical interface mode to static. - ipmi::setDbusProperty( - bus, ipmi::network::SERVICE, networkInterfaceObject.first, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled", false); - - networkInterfacePath = networkInterfaceObject.first; - - // delete all the ipv4 addresses - ipmi::deleteAllDbusObjects(bus, ipmi::network::ROOT, - ipmi::network::IP_INTERFACE, ethIp); - - if (vlanID) + case LanParam::Gateway1: { - ipmi::network::createVLAN(bus, ipmi::network::SERVICE, - ipmi::network::ROOT, ethdevice, vlanID); - - auto networkInterfaceObject = ipmi::getDbusObject( - bus, ipmi::network::VLAN_INTERFACE, ipmi::network::ROOT); - - networkInterfacePath = networkInterfaceObject.first; + auto gateway = + channelCall<getGatewayProperty<AF_INET>>(channel).value_or( + in_addr{}); + ret.pack(dataRef(gateway)); + return responseSuccess(std::move(ret)); } - - if (channelConf->ipsrc == ipmi::network::IPOrigin::DHCP) + case LanParam::Gateway1MAC: { - ipmi::setDbusProperty( - bus, ipmi::network::SERVICE, networkInterfacePath, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled", true); + ether_addr mac{}; + auto neighbor = channelCall<getGatewayNeighbor<AF_INET>>(channel); + if (neighbor) + { + mac = neighbor->mac; + } + ret.pack(dataRef(mac)); + return responseSuccess(std::move(ret)); } - else + case LanParam::VLANId: { - // change the mode to static - ipmi::setDbusProperty( - bus, ipmi::network::SERVICE, networkInterfacePath, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled", false); - - if (!ipaddress.empty()) + uint16_t vlan = channelCall<getVLANProperty>(channel); + if (vlan != 0) { - ipmi::network::createIP(bus, ipmi::network::SERVICE, - networkInterfacePath, ipv4Protocol, - ipaddress, prefix); + vlan |= VLAN_ENABLE_FLAG; } - - if (!gateway.empty()) + else { - ipmi::setDbusProperty(bus, systemObject.second, - systemObject.first, - ipmi::network::SYSTEMCONFIG_INTERFACE, - "DefaultGateway", std::string(gateway)); + vlan = lastDisabledVlan[channel]; } + ret.pack(vlan); + return responseSuccess(std::move(ret)); } - } - catch (sdbusplus::exception::exception& e) - { - log<level::ERR>( - "Failed to set network data", entry("PREFIX=%d", prefix), - entry("ADDRESS=%s", ipaddress.c_str()), - entry("GATEWAY=%s", gateway.c_str()), entry("VLANID=%d", vlanID), - entry("IPSRC=%d", channelConf->ipsrc)); - - commit<InternalFailure>(); - } - - channelConf->clear(); -} - -void commitNetworkChanges() -{ - for (const auto& channel : channelConfig) - { - if (channel.second->flush) + case LanParam::CiphersuiteSupport: + { + if (!listInit) + { + return responseUnspecifiedError(); + } + ret.pack(static_cast<uint8_t>(cipherList.size() - 1)); + return responseSuccess(std::move(ret)); + } + case LanParam::CiphersuiteEntries: + { + 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<uint8_t>(IPFamilyEnables::DualStack)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6Status: { - applyChanges(channel.first); + 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; + if (channelCall<getDHCPProperty>(channel)) + { + control[IPv6RouterControlFlag::Dynamic] = 1; + } + else + { + control[IPv6RouterControlFlag::Static] = 1; + } + ret.pack(control); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6StaticRouter1IP: + { + in6_addr gateway{}; + if (!channelCall<getDHCPProperty>(channel)) + { + gateway = + channelCall<getGatewayProperty<AF_INET6>>(channel).value_or( + in6_addr{}); + } + ret.pack(dataRef(gateway)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6StaticRouter1MAC: + { + ether_addr mac{}; + auto neighbor = channelCall<getGatewayNeighbor<AF_INET6>>(channel); + if (neighbor) + { + mac = neighbor->mac; + } + ret.pack(dataRef(mac)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6StaticRouter1PrefixLength: + { + ret.pack(UINT8_C(0)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6StaticRouter1PrefixValue: + { + in6_addr prefix{}; + ret.pack(dataRef(prefix)); + return responseSuccess(std::move(ret)); } } -} -void createNetworkTimer() -{ - if (!networkTimer) + if ((parameter >= oemCmdStart) && (parameter <= oemCmdEnd)) { - std::function<void()> networkTimerCallback( - std::bind(&commitNetworkChanges)); - - networkTimer = std::make_unique<phosphor::Timer>(networkTimerCallback); + return getLanOem(channel, parameter, set, block); } -} -void register_netfn_transport_functions() -{ - // As this timer is only for transport handler - // so creating it here. - createNetworkTimer(); - // <Wildcard Command> - ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_WILDCARD, NULL, - ipmi_transport_wildcard, PRIVILEGE_USER); + return response(ccParamNotSupported); +} - // <Set LAN Configuration Parameters> - ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_SET_LAN, NULL, - ipmi_transport_set_lan, PRIVILEGE_ADMIN); +} // namespace transport +} // namespace ipmi - // <Get LAN Configuration Parameters> - ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_GET_LAN, NULL, - ipmi_transport_get_lan, PRIVILEGE_OPERATOR); +void register_netfn_transport_functions() __attribute__((constructor)); - return; +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); } |