/* // Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #pragma once #include "app.hpp" #include "dbus_singleton.hpp" #include "dbus_utility.hpp" #include "error_messages.hpp" #include "generated/enums/ethernet_interface.hpp" #include "generated/enums/resource.hpp" #include "human_sort.hpp" #include "query.hpp" #include "registries/privilege_registry.hpp" #include "utils/ip_utils.hpp" #include "utils/json_utils.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace redfish { enum class LinkType { Local, Global }; enum class IpVersion { IpV4, IpV6 }; /** * Structure for keeping IPv4 data required by Redfish */ struct IPv4AddressData { std::string id; std::string address; std::string domain; std::string gateway; std::string netmask; std::string origin; LinkType linktype{}; bool isActive{}; }; /** * Structure for keeping IPv6 data required by Redfish */ struct IPv6AddressData { std::string id; std::string address; std::string origin; uint8_t prefixLength = 0; }; /** * Structure for keeping static route data required by Redfish */ struct StaticGatewayData { std::string id; std::string gateway; size_t prefixLength = 0; std::string protocol; }; /** * Structure for keeping basic single Ethernet Interface information * available from DBus */ struct EthernetInterfaceData { uint32_t speed; size_t mtuSize; bool autoNeg; bool dnsv4Enabled; bool dnsv6Enabled; bool domainv4Enabled; bool domainv6Enabled; bool ntpv4Enabled; bool ntpv6Enabled; bool hostNamev4Enabled; bool hostNamev6Enabled; bool linkUp; bool nicEnabled; bool ipv6AcceptRa; std::string dhcpEnabled; std::string operatingMode; std::string hostName; std::string defaultGateway; std::string ipv6DefaultGateway; std::string ipv6StaticDefaultGateway; std::string macAddress; std::optional vlanId; std::vector nameServers; std::vector staticNameServers; std::vector domainnames; }; struct DHCPParameters { std::optional dhcpv4Enabled; std::optional useDnsServers; std::optional useNtpServers; std::optional useDomainName; std::optional dhcpv6OperatingMode; }; // Helper function that changes bits netmask notation (i.e. /24) // into full dot notation inline std::string getNetmask(unsigned int bits) { uint32_t value = 0xffffffff << (32 - bits); std::string netmask = std::to_string((value >> 24) & 0xff) + "." + std::to_string((value >> 16) & 0xff) + "." + std::to_string((value >> 8) & 0xff) + "." + std::to_string(value & 0xff); return netmask; } inline bool translateDhcpEnabledToBool(const std::string& inputDHCP, bool isIPv4) { if (isIPv4) { return ( (inputDHCP == "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4") || (inputDHCP == "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); } return ((inputDHCP == "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6") || (inputDHCP == "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); } inline std::string getDhcpEnabledEnumeration(bool isIPv4, bool isIPv6) { if (isIPv4 && isIPv6) { return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"; } if (isIPv4) { return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4"; } if (isIPv6) { return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6"; } return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none"; } inline std::string translateAddressOriginDbusToRedfish( const std::string& inputOrigin, bool isIPv4) { if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") { return "Static"; } if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") { if (isIPv4) { return "IPv4LinkLocal"; } return "LinkLocal"; } if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") { if (isIPv4) { return "DHCP"; } return "DHCPv6"; } if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") { return "SLAAC"; } return ""; } inline bool extractEthernetInterfaceData( const std::string& ethifaceId, const dbus::utility::ManagedObjectType& dbusData, EthernetInterfaceData& ethData) { bool idFound = false; for (const auto& objpath : dbusData) { for (const auto& ifacePair : objpath.second) { if (objpath.first == "/xyz/openbmc_project/network/" + ethifaceId) { idFound = true; if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") { for (const auto& propertyPair : ifacePair.second) { if (propertyPair.first == "MACAddress") { const std::string* mac = std::get_if(&propertyPair.second); if (mac != nullptr) { ethData.macAddress = *mac; } } } } else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") { for (const auto& propertyPair : ifacePair.second) { if (propertyPair.first == "Id") { const uint32_t* id = std::get_if(&propertyPair.second); if (id != nullptr) { ethData.vlanId = *id; } } } } else if (ifacePair.first == "xyz.openbmc_project.Network.EthernetInterface") { for (const auto& propertyPair : ifacePair.second) { if (propertyPair.first == "AutoNeg") { const bool* autoNeg = std::get_if(&propertyPair.second); if (autoNeg != nullptr) { ethData.autoNeg = *autoNeg; } } else if (propertyPair.first == "Speed") { const uint32_t* speed = std::get_if(&propertyPair.second); if (speed != nullptr) { ethData.speed = *speed; } } else if (propertyPair.first == "MTU") { const size_t* mtuSize = std::get_if(&propertyPair.second); if (mtuSize != nullptr) { ethData.mtuSize = *mtuSize; } } else if (propertyPair.first == "LinkUp") { const bool* linkUp = std::get_if(&propertyPair.second); if (linkUp != nullptr) { ethData.linkUp = *linkUp; } } else if (propertyPair.first == "NICEnabled") { const bool* nicEnabled = std::get_if(&propertyPair.second); if (nicEnabled != nullptr) { ethData.nicEnabled = *nicEnabled; } } else if (propertyPair.first == "IPv6AcceptRA") { const bool* ipv6AcceptRa = std::get_if(&propertyPair.second); if (ipv6AcceptRa != nullptr) { ethData.ipv6AcceptRa = *ipv6AcceptRa; } } else if (propertyPair.first == "Nameservers") { const std::vector* nameservers = std::get_if>( &propertyPair.second); if (nameservers != nullptr) { ethData.nameServers = *nameservers; } } else if (propertyPair.first == "StaticNameServers") { const std::vector* staticNameServers = std::get_if>( &propertyPair.second); if (staticNameServers != nullptr) { ethData.staticNameServers = *staticNameServers; } } else if (propertyPair.first == "DHCPEnabled") { const std::string* dhcpEnabled = std::get_if(&propertyPair.second); if (dhcpEnabled != nullptr) { ethData.dhcpEnabled = *dhcpEnabled; } } else if (propertyPair.first == "DomainName") { const std::vector* domainNames = std::get_if>( &propertyPair.second); if (domainNames != nullptr) { ethData.domainnames = *domainNames; } } else if (propertyPair.first == "DefaultGateway") { const std::string* defaultGateway = std::get_if(&propertyPair.second); if (defaultGateway != nullptr) { std::string defaultGatewayStr = *defaultGateway; if (defaultGatewayStr.empty()) { ethData.defaultGateway = "0.0.0.0"; } else { ethData.defaultGateway = defaultGatewayStr; } } } else if (propertyPair.first == "DefaultGateway6") { const std::string* defaultGateway6 = std::get_if(&propertyPair.second); if (defaultGateway6 != nullptr) { std::string defaultGateway6Str = *defaultGateway6; if (defaultGateway6Str.empty()) { ethData.ipv6DefaultGateway = "0:0:0:0:0:0:0:0"; } else { ethData.ipv6DefaultGateway = defaultGateway6Str; } } } } } } sdbusplus::message::object_path path( "/xyz/openbmc_project/network"); sdbusplus::message::object_path dhcp4Path = path / ethifaceId / "dhcp4"; if (sdbusplus::message::object_path(objpath.first) == dhcp4Path) { if (ifacePair.first == "xyz.openbmc_project.Network.DHCPConfiguration") { for (const auto& propertyPair : ifacePair.second) { if (propertyPair.first == "DNSEnabled") { const bool* dnsEnabled = std::get_if(&propertyPair.second); if (dnsEnabled != nullptr) { ethData.dnsv4Enabled = *dnsEnabled; } } else if (propertyPair.first == "DomainEnabled") { const bool* domainEnabled = std::get_if(&propertyPair.second); if (domainEnabled != nullptr) { ethData.domainv4Enabled = *domainEnabled; } } else if (propertyPair.first == "NTPEnabled") { const bool* ntpEnabled = std::get_if(&propertyPair.second); if (ntpEnabled != nullptr) { ethData.ntpv4Enabled = *ntpEnabled; } } else if (propertyPair.first == "HostNameEnabled") { const bool* hostNameEnabled = std::get_if(&propertyPair.second); if (hostNameEnabled != nullptr) { ethData.hostNamev4Enabled = *hostNameEnabled; } } } } } sdbusplus::message::object_path dhcp6Path = path / ethifaceId / "dhcp6"; if (sdbusplus::message::object_path(objpath.first) == dhcp6Path) { if (ifacePair.first == "xyz.openbmc_project.Network.DHCPConfiguration") { for (const auto& propertyPair : ifacePair.second) { if (propertyPair.first == "DNSEnabled") { const bool* dnsEnabled = std::get_if(&propertyPair.second); if (dnsEnabled != nullptr) { ethData.dnsv6Enabled = *dnsEnabled; } } if (propertyPair.first == "DomainEnabled") { const bool* domainEnabled = std::get_if(&propertyPair.second); if (domainEnabled != nullptr) { ethData.domainv6Enabled = *domainEnabled; } } else if (propertyPair.first == "NTPEnabled") { const bool* ntpEnabled = std::get_if(&propertyPair.second); if (ntpEnabled != nullptr) { ethData.ntpv6Enabled = *ntpEnabled; } } else if (propertyPair.first == "HostNameEnabled") { const bool* hostNameEnabled = std::get_if(&propertyPair.second); if (hostNameEnabled != nullptr) { ethData.hostNamev6Enabled = *hostNameEnabled; } } } } } // System configuration shows up in the global namespace, so no need // to check eth number if (ifacePair.first == "xyz.openbmc_project.Network.SystemConfiguration") { for (const auto& propertyPair : ifacePair.second) { if (propertyPair.first == "HostName") { const std::string* hostname = std::get_if(&propertyPair.second); if (hostname != nullptr) { ethData.hostName = *hostname; } } } } } } return idFound; } // Helper function that extracts data for single ethernet ipv6 address inline void extractIPV6Data(const std::string& ethifaceId, const dbus::utility::ManagedObjectType& dbusData, std::vector& ipv6Config) { const std::string ipPathStart = "/xyz/openbmc_project/network/" + ethifaceId; // Since there might be several IPv6 configurations aligned with // single ethernet interface, loop over all of them for (const auto& objpath : dbusData) { // Check if proper pattern for object path appears if (objpath.first.str.starts_with(ipPathStart + "/")) { for (const auto& interface : objpath.second) { if (interface.first == "xyz.openbmc_project.Network.IP") { auto type = std::ranges::find_if( interface.second, [](const auto& property) { return property.first == "Type"; }); if (type == interface.second.end()) { continue; } const std::string* typeStr = std::get_if(&type->second); if (typeStr == nullptr || (*typeStr != "xyz.openbmc_project.Network.IP.Protocol.IPv6")) { continue; } // Instance IPv6AddressData structure, and set as // appropriate IPv6AddressData& ipv6Address = ipv6Config.emplace_back(); ipv6Address.id = objpath.first.str.substr(ipPathStart.size()); for (const auto& property : interface.second) { if (property.first == "Address") { const std::string* address = std::get_if(&property.second); if (address != nullptr) { ipv6Address.address = *address; } } else if (property.first == "Origin") { const std::string* origin = std::get_if(&property.second); if (origin != nullptr) { ipv6Address.origin = translateAddressOriginDbusToRedfish(*origin, false); } } else if (property.first == "PrefixLength") { const uint8_t* prefix = std::get_if(&property.second); if (prefix != nullptr) { ipv6Address.prefixLength = *prefix; } } else if (property.first == "Type" || property.first == "Gateway") { // Type & Gateway is not used } else { BMCWEB_LOG_ERROR( "Got extra property: {} on the {} object", property.first, objpath.first.str); } } } } } } } // Helper function that extracts data for single ethernet ipv4 address inline void extractIPData(const std::string& ethifaceId, const dbus::utility::ManagedObjectType& dbusData, std::vector& ipv4Config) { const std::string ipPathStart = "/xyz/openbmc_project/network/" + ethifaceId; // Since there might be several IPv4 configurations aligned with // single ethernet interface, loop over all of them for (const auto& objpath : dbusData) { // Check if proper pattern for object path appears if (objpath.first.str.starts_with(ipPathStart + "/")) { for (const auto& interface : objpath.second) { if (interface.first == "xyz.openbmc_project.Network.IP") { auto type = std::ranges::find_if( interface.second, [](const auto& property) { return property.first == "Type"; }); if (type == interface.second.end()) { continue; } const std::string* typeStr = std::get_if(&type->second); if (typeStr == nullptr || (*typeStr != "xyz.openbmc_project.Network.IP.Protocol.IPv4")) { continue; } // Instance IPv4AddressData structure, and set as // appropriate IPv4AddressData& ipv4Address = ipv4Config.emplace_back(); ipv4Address.id = objpath.first.str.substr(ipPathStart.size()); for (const auto& property : interface.second) { if (property.first == "Address") { const std::string* address = std::get_if(&property.second); if (address != nullptr) { ipv4Address.address = *address; } } else if (property.first == "Origin") { const std::string* origin = std::get_if(&property.second); if (origin != nullptr) { ipv4Address.origin = translateAddressOriginDbusToRedfish(*origin, true); } } else if (property.first == "PrefixLength") { const uint8_t* mask = std::get_if(&property.second); if (mask != nullptr) { // convert it to the string ipv4Address.netmask = getNetmask(*mask); } } else if (property.first == "Type" || property.first == "Gateway") { // Type & Gateway is not used } else { BMCWEB_LOG_ERROR( "Got extra property: {} on the {} object", property.first, objpath.first.str); } } // Check if given address is local, or global ipv4Address.linktype = ipv4Address.address.starts_with("169.254.") ? LinkType::Local : LinkType::Global; } } } } } /** * @brief Modifies the default gateway assigned to the NIC * * @param[in] ifaceId Id of network interface whose default gateway is to be * changed * @param[in] gateway The new gateway value. Assigning an empty string * causes the gateway to be deleted * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void updateIPv4DefaultGateway( const std::string& ifaceId, const std::string& gateway, const std::shared_ptr& asyncResp) { setDbusProperty( asyncResp, "Gateway", "xyz.openbmc_project.Network", sdbusplus::message::object_path("/xyz/openbmc_project/network") / ifaceId, "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway", gateway); } /** * @brief Deletes given static IP address for the interface * * @param[in] ifaceId Id of interface whose IP should be deleted * @param[in] ipHash DBus Hash id of IP that should be deleted * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void deleteIPAddress(const std::string& ifaceId, const std::string& ipHash, const std::shared_ptr& asyncResp) { crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code& ec) { if (ec) { messages::internalError(asyncResp->res); } }, "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/" + ifaceId + ipHash, "xyz.openbmc_project.Object.Delete", "Delete"); } /** * @brief Creates a static IPv4 entry * * @param[in] ifaceId Id of interface upon which to create the IPv4 entry * @param[in] prefixLength IPv4 prefix syntax for the subnet mask * @param[in] gateway IPv4 address of this interfaces gateway * @param[in] address IPv4 address to assign to this interface * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength, const std::string& gateway, const std::string& address, const std::shared_ptr& asyncResp) { auto createIpHandler = [asyncResp, ifaceId, gateway](const boost::system::error_code& ec) { if (ec) { messages::internalError(asyncResp->res); return; } }; crow::connections::systemBus->async_method_call( std::move(createIpHandler), "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/" + ifaceId, "xyz.openbmc_project.Network.IP.Create", "IP", "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, gateway); } /** * @brief Deletes the IP entry for this interface and creates a replacement * static entry * * @param[in] ifaceId Id of interface upon which to create the IPv6 entry * @param[in] id The unique hash entry identifying the DBus entry * @param[in] prefixLength Prefix syntax for the subnet mask * @param[in] address Address to assign to this interface * @param[in] numStaticAddrs Count of IPv4 static addresses * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void deleteAndCreateIPAddress( IpVersion version, const std::string& ifaceId, const std::string& id, uint8_t prefixLength, const std::string& address, const std::string& gateway, const std::shared_ptr& asyncResp) { crow::connections::systemBus->async_method_call( [asyncResp, version, ifaceId, address, prefixLength, gateway](const boost::system::error_code& ec) { if (ec) { messages::internalError(asyncResp->res); } std::string protocol = "xyz.openbmc_project.Network.IP.Protocol."; protocol += version == IpVersion::IpV4 ? "IPv4" : "IPv6"; crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code& ec2) { if (ec2) { messages::internalError(asyncResp->res); } }, "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/" + ifaceId, "xyz.openbmc_project.Network.IP.Create", "IP", protocol, address, prefixLength, gateway); }, "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/" + ifaceId + id, "xyz.openbmc_project.Object.Delete", "Delete"); } inline bool extractIPv6DefaultGatewayData( const std::string& ethifaceId, const dbus::utility::ManagedObjectType& dbusData, std::vector& staticGatewayConfig) { std::string staticGatewayPathStart("/xyz/openbmc_project/network/"); staticGatewayPathStart += ethifaceId; for (const auto& objpath : dbusData) { if (!std::string_view(objpath.first.str) .starts_with(staticGatewayPathStart)) { continue; } for (const auto& interface : objpath.second) { if (interface.first != "xyz.openbmc_project.Network.StaticGateway") { continue; } StaticGatewayData& staticGateway = staticGatewayConfig.emplace_back(); staticGateway.id = objpath.first.filename(); bool success = sdbusplus::unpackPropertiesNoThrow( redfish::dbus_utils::UnpackErrorPrinter(), interface.second, "Gateway", staticGateway.gateway, "PrefixLength", staticGateway.prefixLength, "ProtocolType", staticGateway.protocol); if (!success) { return false; } } } return true; } /** * @brief Creates IPv6 with given data * * @param[in] ifaceId Id of interface whose IP should be added * @param[in] prefixLength Prefix length that needs to be added * @param[in] address IP address that needs to be added * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength, const std::string& address, const std::shared_ptr& asyncResp) { sdbusplus::message::object_path path("/xyz/openbmc_project/network"); path /= ifaceId; auto createIpHandler = [asyncResp, address](const boost::system::error_code& ec) { if (ec) { if (ec == boost::system::errc::io_error) { messages::propertyValueFormatError(asyncResp->res, address, "Address"); } else { messages::internalError(asyncResp->res); } } }; // Passing null for gateway, as per redfish spec IPv6StaticAddresses // object does not have associated gateway property crow::connections::systemBus->async_method_call( std::move(createIpHandler), "xyz.openbmc_project.Network", path, "xyz.openbmc_project.Network.IP.Create", "IP", "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, ""); } /** * @brief Deletes given IPv6 Static Gateway * * @param[in] ifaceId Id of interface whose IP should be deleted * @param[in] ipHash DBus Hash id of IP that should be deleted * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void deleteIPv6Gateway(std::string_view gatewayId, const std::shared_ptr& asyncResp) { sdbusplus::message::object_path path("/xyz/openbmc_project/network"); path /= gatewayId; crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code& ec) { if (ec) { messages::internalError(asyncResp->res); } }, "xyz.openbmc_project.Network", path, "xyz.openbmc_project.Object.Delete", "Delete"); } /** * @brief Creates IPv6 static default gateway with given data * * @param[in] ifaceId Id of interface whose IP should be added * @param[in] prefixLength Prefix length that needs to be added * @param[in] gateway Gateway address that needs to be added * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void createIPv6DefaultGateway( std::string_view ifaceId, size_t prefixLength, std::string_view gateway, const std::shared_ptr& asyncResp) { sdbusplus::message::object_path path("/xyz/openbmc_project/network"); path /= ifaceId; auto createIpHandler = [asyncResp](const boost::system::error_code& ec) { if (ec) { messages::internalError(asyncResp->res); } }; crow::connections::systemBus->async_method_call( std::move(createIpHandler), "xyz.openbmc_project.Network", path, "xyz.openbmc_project.Network.StaticGateway.Create", "StaticGateway", gateway, prefixLength, "xyz.openbmc_project.Network.IP.Protocol.IPv6"); } /** * @brief Deletes the IPv6 default gateway entry for this interface and * creates a replacement IPv6 default gateway entry * * @param[in] ifaceId Id of interface upon which to create the IPv6 * entry * @param[in] gateway IPv6 gateway to assign to this interface * @param[in] prefixLength IPv6 prefix syntax for the subnet mask * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void deleteAndCreateIPv6DefaultGateway( std::string_view ifaceId, std::string_view gatewayId, std::string_view gateway, size_t prefixLength, const std::shared_ptr& asyncResp) { sdbusplus::message::object_path path("/xyz/openbmc_project/network"); path /= gatewayId; crow::connections::systemBus->async_method_call( [asyncResp, ifaceId, gateway, prefixLength](const boost::system::error_code& ec) { if (ec) { messages::internalError(asyncResp->res); return; } createIPv6DefaultGateway(ifaceId, prefixLength, gateway, asyncResp); }, "xyz.openbmc_project.Network", path, "xyz.openbmc_project.Object.Delete", "Delete"); } /** * @brief Sets IPv6 default gateway with given data * * @param[in] ifaceId Id of interface whose gateway should be added * @param[in] input Contains address that needs to be added * @param[in] staticGatewayData Current static gateways in the system * @param[io] asyncResp Response object that will be returned to client * * @return None */ inline void handleIPv6DefaultGateway( const std::string& ifaceId, std::vector>& input, const std::vector& staticGatewayData, const std::shared_ptr& asyncResp) { size_t entryIdx = 1; std::vector::const_iterator staticGatewayEntry = staticGatewayData.begin(); for (std::variant& thisJson : input) { // find the next gateway entry while (staticGatewayEntry != staticGatewayData.end()) { if (staticGatewayEntry->protocol == "xyz.openbmc_project.Network.IP.Protocol.IPv6") { break; } staticGatewayEntry++; } std::string pathString = "IPv6StaticDefaultGateways/" + std::to_string(entryIdx); nlohmann::json::object_t* obj = std::get_if(&thisJson); if (obj == nullptr) { if (staticGatewayEntry == staticGatewayData.end()) { messages::resourceCannotBeDeleted(asyncResp->res); return; } deleteIPv6Gateway(staticGatewayEntry->id, asyncResp); return; } if (obj->empty()) { // Do nothing, but make sure the entry exists. if (staticGatewayEntry == staticGatewayData.end()) { messages::propertyValueFormatError(asyncResp->res, *obj, pathString); return; } } std::optional address; std::optional prefixLength; if (!json_util::readJsonObject(*obj, asyncResp->res, "Address", address, "PrefixLength", prefixLength)) { return; } const std::string* addr = nullptr; size_t prefix = 0; if (address) { addr = &(*address); } else if (staticGatewayEntry != staticGatewayData.end()) { addr = &(staticGatewayEntry->gateway); } else { messages::propertyMissing(asyncResp->res, pathString + "/Address"); return; } if (prefixLength) { prefix = *prefixLength; } else if (staticGatewayEntry != staticGatewayData.end()) { prefix = staticGatewayEntry->prefixLength; } else { messages::propertyMissing(asyncResp->res, pathString + "/PrefixLength"); return; } if (staticGatewayEntry != staticGatewayData.end()) { deleteAndCreateIPv6DefaultGateway(ifaceId, staticGatewayEntry->id, *addr, prefix, asyncResp); staticGatewayEntry++; } else { createIPv6DefaultGateway(ifaceId, prefix, *addr, asyncResp); } entryIdx++; } } /** * Function that retrieves all properties for given Ethernet Interface * Object * from EntityManager Network Manager * @param ethiface_id a eth interface id to query on DBus * @param callback a function that shall be called to convert Dbus output * into JSON */ template void getEthernetIfaceData(const std::string& ethifaceId, CallbackFunc&& callback) { sdbusplus::message::object_path path("/xyz/openbmc_project/network"); dbus::utility::getManagedObjects( "xyz.openbmc_project.Network", path, [ethifaceId{std::string{ethifaceId}}, callback = std::forward(callback)]( const boost::system::error_code& ec, const dbus::utility::ManagedObjectType& resp) mutable { EthernetInterfaceData ethData{}; std::vector ipv4Data; std::vector ipv6Data; std::vector ipv6GatewayData; if (ec) { callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); return; } bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData); if (!found) { callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); return; } extractIPData(ethifaceId, resp, ipv4Data); // Fix global GW for (IPv4AddressData& ipv4 : ipv4Data) { if (((ipv4.linktype == LinkType::Global) && (ipv4.gateway == "0.0.0.0")) || (ipv4.origin == "DHCP") || (ipv4.origin == "Static")) { ipv4.gateway = ethData.defaultGateway; } } extractIPV6Data(ethifaceId, resp, ipv6Data); if (!extractIPv6DefaultGatewayData(ethifaceId, resp, ipv6GatewayData)) { callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); } // Finally make a callback with useful data callback(true, ethData, ipv4Data, ipv6Data, ipv6GatewayData); }); } /** * Function that retrieves all Ethernet Interfaces available through Network * Manager * @param callback a function that shall be called to convert Dbus output * into JSON. */ template void getEthernetIfaceList(CallbackFunc&& callback) { sdbusplus::message::object_path path("/xyz/openbmc_project/network"); dbus::utility::getManagedObjects( "xyz.openbmc_project.Network", path, [callback = std::forward(callback)]( const boost::system::error_code& ec, const dbus::utility::ManagedObjectType& resp) { // Callback requires vector to retrieve all available // ethernet interfaces std::vector ifaceList; ifaceList.reserve(resp.size()); if (ec) { callback(false, ifaceList); return; } // Iterate over all retrieved ObjectPaths. for (const auto& objpath : resp) { // And all interfaces available for certain ObjectPath. for (const auto& interface : objpath.second) { // If interface is // xyz.openbmc_project.Network.EthernetInterface, this is // what we're looking for. if (interface.first == "xyz.openbmc_project.Network.EthernetInterface") { std::string ifaceId = objpath.first.filename(); if (ifaceId.empty()) { continue; } // and put it into output vector. ifaceList.emplace_back(ifaceId); } } } std::ranges::sort(ifaceList, AlphanumLess()); // Finally make a callback with useful data callback(true, ifaceList); }); } inline void handleHostnamePatch(const std::string& hostname, const std::shared_ptr& asyncResp) { // SHOULD handle host names of up to 255 characters(RFC 1123) if (hostname.length() > 255) { messages::propertyValueFormatError(asyncResp->res, hostname, "HostName"); return; } setDbusProperty( asyncResp, "HostName", "xyz.openbmc_project.Network", sdbusplus::message::object_path("/xyz/openbmc_project/network/config"), "xyz.openbmc_project.Network.SystemConfiguration", "HostName", hostname); } inline void handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize, const std::shared_ptr& asyncResp) { sdbusplus::message::object_path objPath("/xyz/openbmc_project/network"); objPath /= ifaceId; setDbusProperty(asyncResp, "MTUSize", "xyz.openbmc_project.Network", objPath, "xyz.openbmc_project.Network.EthernetInterface", "MTU", mtuSize); } inline void handleDomainnamePatch( const std::string& ifaceId, const std::string& domainname, const std::shared_ptr& asyncResp) { std::vector vectorDomainname = {domainname}; setDbusProperty( asyncResp, "FQDN", "xyz.openbmc_project.Network", sdbusplus::message::object_path("/xyz/openbmc_project/network") / ifaceId, "xyz.openbmc_project.Network.EthernetInterface", "DomainName", vectorDomainname); } inline bool isHostnameValid(const std::string& hostname) { // A valid host name can never have the dotted-decimal form (RFC 1123) if (std::ranges::all_of(hostname, ::isdigit)) { return false; } // Each label(hostname/subdomains) within a valid FQDN // MUST handle host names of up to 63 characters (RFC 1123) // labels cannot start or end with hyphens (RFC 952) // labels can start with numbers (RFC 1123) const static std::regex pattern( "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); return std::regex_match(hostname, pattern); } inline bool isDomainnameValid(const std::string& domainname) { // Can have multiple subdomains // Top Level Domain's min length is 2 character const static std::regex pattern( "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$"); return std::regex_match(domainname, pattern); } inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn, const std::shared_ptr& asyncResp) { // Total length of FQDN must not exceed 255 characters(RFC 1035) if (fqdn.length() > 255) { messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); return; } size_t pos = fqdn.find('.'); if (pos == std::string::npos) { messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); return; } std::string hostname; std::string domainname; domainname = (fqdn).substr(pos + 1); hostname = (fqdn).substr(0, pos); if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) { messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); return; } handleHostnamePatch(hostname, asyncResp); handleDomainnamePatch(ifaceId, domainname, asyncResp); } inline void handleMACAddressPatch( const std::string& ifaceId, const std::string& macAddress, const std::shared_ptr& asyncResp) { setDbusProperty( asyncResp, "MACAddress", "xyz.openbmc_project.Network", sdbusplus::message::object_path("/xyz/openbmc_project/network") / ifaceId, "xyz.openbmc_project.Network.MACAddress", "MACAddress", macAddress); } inline void setDHCPEnabled(const std::string& ifaceId, const std::string& propertyName, const bool v4Value, const bool v6Value, const std::shared_ptr& asyncResp) { const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value); setDbusProperty( asyncResp, "DHCPv4", "xyz.openbmc_project.Network", sdbusplus::message::object_path("/xyz/openbmc_project/network") / ifaceId, "xyz.openbmc_project.Network.EthernetInterface", propertyName, dhcp); } enum class NetworkType { dhcp4, dhcp6 }; inline void setDHCPConfig(const std::string& propertyName, const bool& value, const std::shared_ptr& asyncResp, const std::string& ethifaceId, NetworkType type) { BMCWEB_LOG_DEBUG("{} = {}", propertyName, value); std::string redfishPropertyName; sdbusplus::message::object_path path("/xyz/openbmc_project/network/"); path /= ethifaceId; if (type == NetworkType::dhcp4) { path /= "dhcp4"; redfishPropertyName = "DHCPv4"; } else { path /= "dhcp6"; redfishPropertyName = "DHCPv6"; } setDbusProperty( asyncResp, redfishPropertyName, "xyz.openbmc_project.Network", path, "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, value); } inline void handleSLAACAutoConfigPatch( const std::string& ifaceId, bool ipv6AutoConfigEnabled, const std::shared_ptr& asyncResp) { sdbusplus::message::object_path path("/xyz/openbmc_project/network"); path /= ifaceId; setDbusProperty(asyncResp, "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", "xyz.openbmc_project.Network", path, "xyz.openbmc_project.Network.EthernetInterface", "IPv6AcceptRA", ipv6AutoConfigEnabled); } inline void handleDHCPPatch( const std::string& ifaceId, const EthernetInterfaceData& ethData, const DHCPParameters& v4dhcpParms, const DHCPParameters& v6dhcpParms, const std::shared_ptr& asyncResp) { bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true); bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false); if (ipv4Active) { updateIPv4DefaultGateway(ifaceId, "", asyncResp); } bool nextv4DHCPState = v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; bool nextv6DHCPState{}; if (v6dhcpParms.dhcpv6OperatingMode) { if ((*v6dhcpParms.dhcpv6OperatingMode != "Enabled") && (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) { messages::propertyValueFormatError(asyncResp->res, *v6dhcpParms.dhcpv6OperatingMode, "OperatingMode"); return; } nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Enabled"); } else { nextv6DHCPState = ipv6Active; } bool nextDNSv4 = ethData.dnsv4Enabled; bool nextDNSv6 = ethData.dnsv6Enabled; if (v4dhcpParms.useDnsServers) { nextDNSv4 = *v4dhcpParms.useDnsServers; } if (v6dhcpParms.useDnsServers) { nextDNSv6 = *v6dhcpParms.useDnsServers; } bool nextNTPv4 = ethData.ntpv4Enabled; bool nextNTPv6 = ethData.ntpv6Enabled; if (v4dhcpParms.useNtpServers) { nextNTPv4 = *v4dhcpParms.useNtpServers; } if (v6dhcpParms.useNtpServers) { nextNTPv6 = *v6dhcpParms.useNtpServers; } bool nextUsev4Domain = ethData.domainv4Enabled; bool nextUsev6Domain = ethData.domainv6Enabled; if (v4dhcpParms.useDomainName) { nextUsev4Domain = *v4dhcpParms.useDomainName; } if (v6dhcpParms.useDomainName) { nextUsev6Domain = *v6dhcpParms.useDomainName; } BMCWEB_LOG_DEBUG("set DHCPEnabled..."); setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, asyncResp); BMCWEB_LOG_DEBUG("set DNSEnabled..."); setDHCPConfig("DNSEnabled", nextDNSv4, asyncResp, ifaceId, NetworkType::dhcp4); BMCWEB_LOG_DEBUG("set NTPEnabled..."); setDHCPConfig("NTPEnabled", nextNTPv4, asyncResp, ifaceId, NetworkType::dhcp4); BMCWEB_LOG_DEBUG("set DomainEnabled..."); setDHCPConfig("DomainEnabled", nextUsev4Domain, asyncResp, ifaceId, NetworkType::dhcp4); BMCWEB_LOG_DEBUG("set DNSEnabled for dhcp6..."); setDHCPConfig("DNSEnabled", nextDNSv6, asyncResp, ifaceId, NetworkType::dhcp6); BMCWEB_LOG_DEBUG("set NTPEnabled for dhcp6..."); setDHCPConfig("NTPEnabled", nextNTPv6, asyncResp, ifaceId, NetworkType::dhcp6); BMCWEB_LOG_DEBUG("set DomainEnabled for dhcp6..."); setDHCPConfig("DomainEnabled", nextUsev6Domain, asyncResp, ifaceId, NetworkType::dhcp6); } inline std::vector::const_iterator getNextStaticIpEntry( const std::vector::const_iterator& head, const std::vector::const_iterator& end) { return std::find_if(head, end, [](const IPv4AddressData& value) { return value.origin == "Static"; }); } inline std::vector::const_iterator getNextStaticIpEntry( const std::vector::const_iterator& head, const std::vector::const_iterator& end) { return std::find_if(head, end, [](const IPv6AddressData& value) { return value.origin == "Static"; }); } inline void handleIPv4StaticPatch( const std::string& ifaceId, std::vector>& input, const EthernetInterfaceData& ethData, const std::vector& ipv4Data, const std::shared_ptr& asyncResp) { unsigned entryIdx = 1; // Find the first static IP address currently active on the NIC and // match it to the first JSON element in the IPv4StaticAddresses array. // Match each subsequent JSON element to the next static IP programmed // into the NIC. std::vector::const_iterator nicIpEntry = getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend()); bool gatewayValueAssigned{}; bool preserveGateway{}; std::string activePath{}; std::string activeGateway{}; if (!ethData.defaultGateway.empty() && ethData.defaultGateway != "0.0.0.0") { // The NIC is already configured with a default gateway. Use this if // the leading entry in the PATCH is '{}', which is preserving an active // static address. activeGateway = ethData.defaultGateway; activePath = "IPv4StaticAddresses/1"; gatewayValueAssigned = true; } for (std::variant& thisJson : input) { std::string pathString = "IPv4StaticAddresses/" + std::to_string(entryIdx); nlohmann::json::object_t* obj = std::get_if(&thisJson); if (obj == nullptr) { if (nicIpEntry != ipv4Data.cend()) { deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp); nicIpEntry = getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); if (!preserveGateway && (nicIpEntry == ipv4Data.cend())) { // All entries have been processed, and this last has // requested the IP address be deleted. No prior entry // performed an action that created or modified a // gateway. Deleting this IP address means the default // gateway entry has to be removed as well. updateIPv4DefaultGateway(ifaceId, "", asyncResp); } entryIdx++; continue; } // Received a DELETE action on an entry not assigned to the NIC messages::resourceCannotBeDeleted(asyncResp->res); return; } // An Add/Modify action is requested if (!obj->empty()) { std::optional address; std::optional subnetMask; std::optional gateway; if (!json_util::readJsonObject(*obj, asyncResp->res, "Address", address, "SubnetMask", subnetMask, "Gateway", gateway)) { messages::propertyValueFormatError(asyncResp->res, *obj, pathString); return; } // Find the address/subnet/gateway values. Any values that are // not explicitly provided are assumed to be unmodified from the // current state of the interface. Merge existing state into the // current request. if (address) { if (!ip_util::ipv4VerifyIpAndGetBitcount(*address)) { messages::propertyValueFormatError(asyncResp->res, *address, pathString + "/Address"); return; } } else if (nicIpEntry != ipv4Data.cend()) { address = (nicIpEntry->address); } else { messages::propertyMissing(asyncResp->res, pathString + "/Address"); return; } uint8_t prefixLength = 0; if (subnetMask) { if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) { messages::propertyValueFormatError( asyncResp->res, *subnetMask, pathString + "/SubnetMask"); return; } } else if (nicIpEntry != ipv4Data.cend()) { if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, &prefixLength)) { messages::propertyValueFormatError( asyncResp->res, nicIpEntry->netmask, pathString + "/SubnetMask"); return; } } else { messages::propertyMissing(asyncResp->res, pathString + "/SubnetMask"); return; } if (gateway) { if (!ip_util::ipv4VerifyIpAndGetBitcount(*gateway)) { messages::propertyValueFormatError(asyncResp->res, *gateway, pathString + "/Gateway"); return; } } else if (nicIpEntry != ipv4Data.cend()) { gateway = nicIpEntry->gateway; } else { messages::propertyMissing(asyncResp->res, pathString + "/Gateway"); return; } if (gatewayValueAssigned) { if (activeGateway != gateway) { // A NIC can only have a single active gateway value. // If any gateway in the array of static addresses // mismatch the PATCH is in error. std::string arg1 = pathString + "/Gateway"; std::string arg2 = activePath + "/Gateway"; messages::propertyValueConflict(asyncResp->res, arg1, arg2); return; } } else { // Capture the very first gateway value from the incoming // JSON record and use it at the default gateway. updateIPv4DefaultGateway(ifaceId, *gateway, asyncResp); activeGateway = *gateway; activePath = pathString; gatewayValueAssigned = true; } if (nicIpEntry != ipv4Data.cend()) { deleteAndCreateIPAddress(IpVersion::IpV4, ifaceId, nicIpEntry->id, prefixLength, *address, *gateway, asyncResp); nicIpEntry = getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); preserveGateway = true; } else { createIPv4(ifaceId, prefixLength, *gateway, *address, asyncResp); preserveGateway = true; } entryIdx++; } else { // Received {}, do not modify this address if (nicIpEntry != ipv4Data.cend()) { nicIpEntry = getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); preserveGateway = true; entryIdx++; } else { // Requested a DO NOT MODIFY action on an entry not assigned // to the NIC messages::propertyValueFormatError(asyncResp->res, *obj, pathString); return; } } } } inline void handleStaticNameServersPatch( const std::string& ifaceId, const std::vector& updatedStaticNameServers, const std::shared_ptr& asyncResp) { setDbusProperty( asyncResp, "StaticNameServers", "xyz.openbmc_project.Network", sdbusplus::message::object_path("/xyz/openbmc_project/network") / ifaceId, "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", updatedStaticNameServers); } inline void handleIPv6StaticAddressesPatch( const std::string& ifaceId, std::vector>& input, const std::vector& ipv6Data, const std::shared_ptr& asyncResp) { size_t entryIdx = 1; std::vector::const_iterator nicIpEntry = getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); for (std::variant& thisJson : input) { std::string pathString = "IPv6StaticAddresses/" + std::to_string(entryIdx); nlohmann::json::object_t* obj = std::get_if(&thisJson); if (obj != nullptr && !obj->empty()) { std::optional address; std::optional prefixLength; nlohmann::json::object_t thisJsonCopy = *obj; if (!json_util::readJsonObject(thisJsonCopy, asyncResp->res, "Address", address, "PrefixLength", prefixLength)) { messages::propertyValueFormatError(asyncResp->res, thisJsonCopy, pathString); return; } // Find the address and prefixLength values. Any values that are // not explicitly provided are assumed to be unmodified from the // current state of the interface. Merge existing state into the // current request. if (!address) { if (nicIpEntry == ipv6Data.end()) { messages::propertyMissing(asyncResp->res, pathString + "/Address"); return; } address = nicIpEntry->address; } if (!prefixLength) { if (nicIpEntry == ipv6Data.end()) { messages::propertyMissing(asyncResp->res, pathString + "/PrefixLength"); return; } prefixLength = nicIpEntry->prefixLength; } if (nicIpEntry != ipv6Data.end()) { deleteAndCreateIPAddress(IpVersion::IpV6, ifaceId, nicIpEntry->id, *prefixLength, *address, "", asyncResp); nicIpEntry = getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); } else { createIPv6(ifaceId, *prefixLength, *address, asyncResp); } entryIdx++; } else { if (nicIpEntry == ipv6Data.end()) { // Requesting a DELETE/DO NOT MODIFY action for an item // that isn't present on the eth(n) interface. Input JSON is // in error, so bail out. if (obj == nullptr) { messages::resourceCannotBeDeleted(asyncResp->res); return; } messages::propertyValueFormatError(asyncResp->res, *obj, pathString); return; } if (obj == nullptr) { deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp); } if (nicIpEntry != ipv6Data.cend()) { nicIpEntry = getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); } entryIdx++; } } } inline std::string extractParentInterfaceName(const std::string& ifaceId) { std::size_t pos = ifaceId.find('_'); return ifaceId.substr(0, pos); } inline void parseInterfaceData( const std::shared_ptr& asyncResp, const std::string& ifaceId, const EthernetInterfaceData& ethData, const std::vector& ipv4Data, const std::vector& ipv6Data, const std::vector& ipv6GatewayData) { nlohmann::json& jsonResponse = asyncResp->res.jsonValue; jsonResponse["Id"] = ifaceId; jsonResponse["@odata.id"] = boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}", BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceId); jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; if (ethData.nicEnabled) { jsonResponse["LinkStatus"] = ethData.linkUp ? ethernet_interface::LinkStatus::LinkUp : ethernet_interface::LinkStatus::LinkDown; jsonResponse["Status"]["State"] = resource::State::Enabled; } else { jsonResponse["LinkStatus"] = ethernet_interface::LinkStatus::NoLink; jsonResponse["Status"]["State"] = resource::State::Disabled; } jsonResponse["SpeedMbps"] = ethData.speed; jsonResponse["MTUSize"] = ethData.mtuSize; jsonResponse["MACAddress"] = ethData.macAddress; jsonResponse["DHCPv4"]["DHCPEnabled"] = translateDhcpEnabledToBool(ethData.dhcpEnabled, true); jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled; jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled; jsonResponse["DHCPv4"]["UseDomainName"] = ethData.domainv4Enabled; jsonResponse["DHCPv6"]["OperatingMode"] = translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Enabled" : "Disabled"; jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled; jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled; jsonResponse["DHCPv6"]["UseDomainName"] = ethData.domainv6Enabled; jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] = ethData.ipv6AcceptRa; if (!ethData.hostName.empty()) { jsonResponse["HostName"] = ethData.hostName; // When domain name is empty then it means, that it is a network // without domain names, and the host name itself must be treated as // FQDN std::string fqdn = ethData.hostName; if (!ethData.domainnames.empty()) { fqdn += "." + ethData.domainnames[0]; } jsonResponse["FQDN"] = fqdn; } if (ethData.vlanId) { jsonResponse["EthernetInterfaceType"] = ethernet_interface::EthernetDeviceType::Virtual; jsonResponse["VLAN"]["VLANEnable"] = true; jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId; jsonResponse["VLAN"]["Tagged"] = true; nlohmann::json::array_t relatedInterfaces; nlohmann::json& parentInterface = relatedInterfaces.emplace_back(); parentInterface["@odata.id"] = boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces", BMCWEB_REDFISH_MANAGER_URI_NAME, extractParentInterfaceName(ifaceId)); jsonResponse["Links"]["RelatedInterfaces"] = std::move(relatedInterfaces); } else { jsonResponse["EthernetInterfaceType"] = ethernet_interface::EthernetDeviceType::Physical; } jsonResponse["NameServers"] = ethData.nameServers; jsonResponse["StaticNameServers"] = ethData.staticNameServers; nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; ipv4Array = nlohmann::json::array(); ipv4StaticArray = nlohmann::json::array(); for (const auto& ipv4Config : ipv4Data) { std::string gatewayStr = ipv4Config.gateway; if (gatewayStr.empty()) { gatewayStr = "0.0.0.0"; } nlohmann::json::object_t ipv4; ipv4["AddressOrigin"] = ipv4Config.origin; ipv4["SubnetMask"] = ipv4Config.netmask; ipv4["Address"] = ipv4Config.address; ipv4["Gateway"] = gatewayStr; if (ipv4Config.origin == "Static") { ipv4StaticArray.push_back(ipv4); } ipv4Array.emplace_back(std::move(ipv4)); } std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; if (ipv6GatewayStr.empty()) { ipv6GatewayStr = "0:0:0:0:0:0:0:0"; } jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; nlohmann::json::array_t ipv6StaticGatewayArray; for (const auto& ipv6GatewayConfig : ipv6GatewayData) { nlohmann::json::object_t ipv6Gateway; ipv6Gateway["Address"] = ipv6GatewayConfig.gateway; ipv6Gateway["PrefixLength"] = ipv6GatewayConfig.prefixLength; ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway)); } jsonResponse["IPv6StaticDefaultGateways"] = std::move(ipv6StaticGatewayArray); nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; ipv6Array = nlohmann::json::array(); ipv6StaticArray = nlohmann::json::array(); nlohmann::json& ipv6AddrPolicyTable = jsonResponse["IPv6AddressPolicyTable"]; ipv6AddrPolicyTable = nlohmann::json::array(); for (const auto& ipv6Config : ipv6Data) { nlohmann::json::object_t ipv6; ipv6["Address"] = ipv6Config.address; ipv6["PrefixLength"] = ipv6Config.prefixLength; ipv6["AddressOrigin"] = ipv6Config.origin; ipv6Array.emplace_back(std::move(ipv6)); if (ipv6Config.origin == "Static") { nlohmann::json::object_t ipv6Static; ipv6Static["Address"] = ipv6Config.address; ipv6Static["PrefixLength"] = ipv6Config.prefixLength; ipv6StaticArray.emplace_back(std::move(ipv6Static)); } } } inline void afterDelete(const std::shared_ptr& asyncResp, const std::string& ifaceId, const boost::system::error_code& ec, const sdbusplus::message_t& m) { if (!ec) { return; } const sd_bus_error* dbusError = m.get_error(); if (dbusError == nullptr) { messages::internalError(asyncResp->res); return; } BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") == dbusError->name) { messages::resourceNotFound(asyncResp->res, "EthernetInterface", ifaceId); return; } if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") == dbusError->name) { messages::resourceCannotBeDeleted(asyncResp->res); return; } messages::internalError(asyncResp->res); } inline void afterVlanCreate( const std::shared_ptr& asyncResp, const std::string& parentInterfaceUri, const std::string& vlanInterface, const boost::system::error_code& ec, const sdbusplus::message_t& m ) { if (ec) { const sd_bus_error* dbusError = m.get_error(); if (dbusError == nullptr) { messages::internalError(asyncResp->res); return; } BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); if (std::string_view( "xyz.openbmc_project.Common.Error.ResourceNotFound") == dbusError->name) { messages::propertyValueNotInList( asyncResp->res, parentInterfaceUri, "Links/RelatedInterfaces/0/@odata.id"); return; } if (std::string_view( "xyz.openbmc_project.Common.Error.InvalidArgument") == dbusError->name) { messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface", "Id", vlanInterface); return; } messages::internalError(asyncResp->res); return; } const boost::urls::url vlanInterfaceUri = boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}", BMCWEB_REDFISH_MANAGER_URI_NAME, vlanInterface); asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer()); } inline void requestEthernetInterfacesRoutes(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Managers//EthernetInterfaces/") .privileges(redfish::privileges::getEthernetInterfaceCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& managerId) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) { messages::resourceNotFound(asyncResp->res, "Manager", managerId); return; } asyncResp->res.jsonValue["@odata.type"] = "#EthernetInterfaceCollection.EthernetInterfaceCollection"; asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( "/redfish/v1/Managers/{}/EthernetInterfaces", BMCWEB_REDFISH_MANAGER_URI_NAME); asyncResp->res.jsonValue["Name"] = "Ethernet Network Interface Collection"; asyncResp->res.jsonValue["Description"] = "Collection of EthernetInterfaces for this Manager"; // Get eth interface list, and call the below callback for JSON // preparation getEthernetIfaceList( [asyncResp](const bool& success, const std::vector& ifaceList) { if (!success) { messages::internalError(asyncResp->res); return; } nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; ifaceArray = nlohmann::json::array(); for (const std::string& ifaceItem : ifaceList) { nlohmann::json::object_t iface; iface["@odata.id"] = boost::urls::format( "/redfish/v1/Managers/{}/EthernetInterfaces/{}", BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceItem); ifaceArray.push_back(std::move(iface)); } asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( "/redfish/v1/Managers/{}/EthernetInterfaces", BMCWEB_REDFISH_MANAGER_URI_NAME); }); }); BMCWEB_ROUTE(app, "/redfish/v1/Managers//EthernetInterfaces/") .privileges(redfish::privileges::postEthernetInterfaceCollection) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& managerId) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) { messages::resourceNotFound(asyncResp->res, "Manager", managerId); return; } bool vlanEnable = false; uint32_t vlanId = 0; std::vector relatedInterfaces; if (!json_util::readJsonPatch( req, asyncResp->res, "VLAN/VLANEnable", vlanEnable, "VLAN/VLANId", vlanId, "Links/RelatedInterfaces", relatedInterfaces)) { return; } if (relatedInterfaces.size() != 1) { messages::arraySizeTooLong(asyncResp->res, "Links/RelatedInterfaces", relatedInterfaces.size()); return; } std::string parentInterfaceUri; if (!json_util::readJsonObject(relatedInterfaces[0], asyncResp->res, "@odata.id", parentInterfaceUri)) { messages::propertyMissing( asyncResp->res, "Links/RelatedInterfaces/0/@odata.id"); return; } BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri); boost::system::result parsedUri = boost::urls::parse_relative_ref(parentInterfaceUri); if (!parsedUri) { messages::propertyValueFormatError( asyncResp->res, parentInterfaceUri, "Links/RelatedInterfaces/0/@odata.id"); return; } std::string parentInterface; if (!crow::utility::readUrlSegments( *parsedUri, "redfish", "v1", "Managers", "bmc", "EthernetInterfaces", std::ref(parentInterface))) { messages::propertyValueNotInList( asyncResp->res, parentInterfaceUri, "Links/RelatedInterfaces/0/@odata.id"); return; } if (!vlanEnable) { // In OpenBMC implementation, VLANEnable cannot be false on // create messages::propertyValueIncorrect( asyncResp->res, "VLAN/VLANEnable", "false"); return; } std::string vlanInterface = parentInterface + "_" + std::to_string(vlanId); crow::connections::systemBus->async_method_call( [asyncResp, parentInterfaceUri, vlanInterface](const boost::system::error_code& ec, const sdbusplus::message_t& m) { afterVlanCreate(asyncResp, parentInterfaceUri, vlanInterface, ec, m); }, "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", "xyz.openbmc_project.Network.VLAN.Create", "VLAN", parentInterface, vlanId); }); BMCWEB_ROUTE(app, "/redfish/v1/Managers//EthernetInterfaces//") .privileges(redfish::privileges::getEthernetInterface) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& managerId, const std::string& ifaceId) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) { messages::resourceNotFound(asyncResp->res, "Manager", managerId); return; } getEthernetIfaceData( ifaceId, [asyncResp, ifaceId]( const bool& success, const EthernetInterfaceData& ethData, const std::vector& ipv4Data, const std::vector& ipv6Data, const std::vector& ipv6GatewayData) { if (!success) { // TODO(Pawel)consider distinguish between non // existing object, and other errors messages::resourceNotFound( asyncResp->res, "EthernetInterface", ifaceId); return; } asyncResp->res.jsonValue["@odata.type"] = "#EthernetInterface.v1_9_0.EthernetInterface"; asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; asyncResp->res.jsonValue["Description"] = "Management Network Interface"; parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data, ipv6GatewayData); }); }); BMCWEB_ROUTE(app, "/redfish/v1/Managers//EthernetInterfaces//") .privileges(redfish::privileges::patchEthernetInterface) .methods(boost::beast::http::verb::patch)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& managerId, const std::string& ifaceId) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) { messages::resourceNotFound(asyncResp->res, "Manager", managerId); return; } std::optional hostname; std::optional fqdn; std::optional macAddress; std::optional ipv6DefaultGateway; std::optional>> ipv4StaticAddresses; std::optional>> ipv6StaticAddresses; std::optional>> ipv6StaticDefaultGateways; std::optional> staticNameServers; std::optional ipv6AutoConfigEnabled; std::optional interfaceEnabled; std::optional mtuSize; DHCPParameters v4dhcpParms; DHCPParameters v6dhcpParms; // clang-format off if (!json_util::readJsonPatch(req, asyncResp->res, "DHCPv4/DHCPEnabled", v4dhcpParms.dhcpv4Enabled, "DHCPv4/UseDNSServers", v4dhcpParms.useDnsServers, "DHCPv4/UseDomainName", v4dhcpParms.useDomainName, "DHCPv4/UseNTPServers", v4dhcpParms.useNtpServers, "DHCPv6/OperatingMode", v6dhcpParms.dhcpv6OperatingMode, "DHCPv6/UseDNSServers", v6dhcpParms.useDnsServers, "DHCPv6/UseDomainName", v6dhcpParms.useDomainName, "DHCPv6/UseNTPServers", v6dhcpParms.useNtpServers, "FQDN", fqdn, "HostName", hostname, "IPv4StaticAddresses", ipv4StaticAddresses, "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses", ipv6StaticAddresses, "IPv6StaticDefaultGateways", ipv6StaticDefaultGateways, "InterfaceEnabled", interfaceEnabled, "MACAddress", macAddress, "MTUSize", mtuSize, "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", ipv6AutoConfigEnabled, "StaticNameServers", staticNameServers ) ) { return; } // clang-format on // Get single eth interface data, and call the below callback // for JSON preparation getEthernetIfaceData( ifaceId, [asyncResp, ifaceId, hostname = std::move(hostname), fqdn = std::move(fqdn), macAddress = std::move(macAddress), ipv4StaticAddresses = std::move(ipv4StaticAddresses), ipv6DefaultGateway = std::move(ipv6DefaultGateway), ipv6StaticAddresses = std::move(ipv6StaticAddresses), ipv6StaticDefaultGateway = std::move(ipv6StaticDefaultGateways), staticNameServers = std::move(staticNameServers), mtuSize, ipv6AutoConfigEnabled, v4dhcpParms = std::move(v4dhcpParms), v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( const bool success, const EthernetInterfaceData& ethData, const std::vector& ipv4Data, const std::vector& ipv6Data, const std::vector& ipv6GatewayData) mutable { if (!success) { // ... otherwise return error // TODO(Pawel)consider distinguish between non // existing object, and other errors messages::resourceNotFound( asyncResp->res, "EthernetInterface", ifaceId); return; } handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, asyncResp); if (hostname) { handleHostnamePatch(*hostname, asyncResp); } if (ipv6AutoConfigEnabled) { handleSLAACAutoConfigPatch( ifaceId, *ipv6AutoConfigEnabled, asyncResp); } if (fqdn) { handleFqdnPatch(ifaceId, *fqdn, asyncResp); } if (macAddress) { handleMACAddressPatch(ifaceId, *macAddress, asyncResp); } if (ipv4StaticAddresses) { handleIPv4StaticPatch(ifaceId, *ipv4StaticAddresses, ethData, ipv4Data, asyncResp); } if (staticNameServers) { handleStaticNameServersPatch( ifaceId, *staticNameServers, asyncResp); } if (ipv6DefaultGateway) { messages::propertyNotWritable(asyncResp->res, "IPv6DefaultGateway"); } if (ipv6StaticAddresses) { handleIPv6StaticAddressesPatch(ifaceId, *ipv6StaticAddresses, ipv6Data, asyncResp); } if (ipv6StaticDefaultGateway) { handleIPv6DefaultGateway( ifaceId, *ipv6StaticDefaultGateway, ipv6GatewayData, asyncResp); } if (interfaceEnabled) { setDbusProperty( asyncResp, "InterfaceEnabled", "xyz.openbmc_project.Network", sdbusplus::message::object_path( "/xyz/openbmc_project/network") / ifaceId, "xyz.openbmc_project.Network.EthernetInterface", "NICEnabled", *interfaceEnabled); } if (mtuSize) { handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); } }); }); BMCWEB_ROUTE(app, "/redfish/v1/Managers//EthernetInterfaces//") .privileges(redfish::privileges::deleteEthernetInterface) .methods(boost::beast::http::verb::delete_)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& managerId, const std::string& ifaceId) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) { messages::resourceNotFound(asyncResp->res, "Manager", managerId); return; } crow::connections::systemBus->async_method_call( [asyncResp, ifaceId](const boost::system::error_code& ec, const sdbusplus::message_t& m) { afterDelete(asyncResp, ifaceId, ec, m); }, "xyz.openbmc_project.Network", std::string("/xyz/openbmc_project/network/") + ifaceId, "xyz.openbmc_project.Object.Delete", "Delete"); }); } } // namespace redfish