1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "node.hpp" 19 #include <boost/container/flat_map.hpp> 20 21 namespace redfish { 22 23 /** 24 * DBus types primitives for several generic DBus interfaces 25 * TODO(Pawel) consider move this to separate file into boost::dbus 26 */ 27 using PropertiesMapType = 28 boost::container::flat_map<std::string, dbus::dbus_variant>; 29 30 using GetManagedObjectsType = boost::container::flat_map< 31 dbus::object_path, 32 boost::container::flat_map<std::string, PropertiesMapType>>; 33 34 using GetAllPropertiesType = PropertiesMapType; 35 36 /** 37 * Structure for keeping IPv4 data required by Redfish 38 * TODO(Pawel) consider change everything to ptr, or to non-ptr values. 39 */ 40 struct IPv4AddressData { 41 const std::string *address; 42 const std::string *domain; 43 const std::string *gateway; 44 std::string netmask; 45 std::string origin; 46 bool global; 47 }; 48 49 /** 50 * Structure for keeping basic single Ethernet Interface information 51 * available from DBus 52 */ 53 struct EthernetInterfaceData { 54 const unsigned int *speed; 55 const bool *auto_neg; 56 const std::string *hostname; 57 const std::string *default_gateway; 58 const std::string *mac_address; 59 const unsigned int *vlan_id; 60 }; 61 62 /** 63 * OnDemandEthernetProvider 64 * Ethernet provider class that retrieves data directly from dbus, before seting 65 * it into JSON output. This does not cache any data. 66 * 67 * TODO(Pawel) 68 * This perhaps shall be different file, which has to be chosen on compile time 69 * depending on OEM needs 70 */ 71 class OnDemandEthernetProvider { 72 private: 73 // Consts that may have influence on EthernetProvider performance/memory usage 74 const size_t MAX_IPV4_ADDRESSES_PER_INTERFACE = 10; 75 76 // Helper function that allows to extract GetAllPropertiesType from 77 // GetManagedObjectsType, based on object path, and interface name 78 const PropertiesMapType *extractInterfaceProperties( 79 const dbus::object_path &objpath, const std::string &interface, 80 const GetManagedObjectsType &dbus_data) { 81 const auto &dbus_obj = dbus_data.find(objpath); 82 if (dbus_obj != dbus_data.end()) { 83 const auto &iface = dbus_obj->second.find(interface); 84 if (iface != dbus_obj->second.end()) { 85 return &iface->second; 86 } 87 } 88 return nullptr; 89 } 90 91 // Helper Wrapper that does inline object_path conversion from string 92 // into dbus::object_path type 93 inline const PropertiesMapType *extractInterfaceProperties( 94 const std::string &objpath, const std::string &interface, 95 const GetManagedObjectsType &dbus_data) { 96 const auto &dbus_obj = dbus::object_path{objpath}; 97 return extractInterfaceProperties(dbus_obj, interface, dbus_data); 98 } 99 100 // Helper function that allows to get pointer to the property from 101 // GetAllPropertiesType native, or extracted by GetAllPropertiesType 102 template <typename T> 103 inline const T *extractProperty(const PropertiesMapType &properties, 104 const std::string &name) { 105 const auto &property = properties.find(name); 106 if (property != properties.end()) { 107 return boost::get<T>(&property->second); 108 } 109 return nullptr; 110 } 111 // TODO(Pawel) Consider to move the above functions to dbus 112 // generic_interfaces.hpp 113 114 // Helper function that extracts data from several dbus objects and several 115 // interfaces required by single ethernet interface instance 116 void extractEthernetInterfaceData(const std::string ðiface_id, 117 const GetManagedObjectsType &dbus_data, 118 EthernetInterfaceData ð_data) { 119 // Extract data that contains MAC Address 120 const PropertiesMapType *mac_properties = extractInterfaceProperties( 121 "/xyz/openbmc_project/network/" + ethiface_id, 122 "xyz.openbmc_project.Network.MACAddress", dbus_data); 123 124 if (mac_properties != nullptr) { 125 eth_data.mac_address = 126 extractProperty<std::string>(*mac_properties, "MACAddress"); 127 } 128 129 const PropertiesMapType *vlan_properties = extractInterfaceProperties( 130 "/xyz/openbmc_project/network/" + ethiface_id, 131 "xyz.openbmc_project.Network.VLAN", dbus_data); 132 133 if (vlan_properties != nullptr) { 134 eth_data.vlan_id = extractProperty<unsigned int>(*vlan_properties, "Id"); 135 } 136 137 // Extract data that contains link information (auto negotiation and speed) 138 const PropertiesMapType *eth_properties = extractInterfaceProperties( 139 "/xyz/openbmc_project/network/" + ethiface_id, 140 "xyz.openbmc_project.Network.EthernetInterface", dbus_data); 141 142 if (eth_properties != nullptr) { 143 eth_data.auto_neg = extractProperty<bool>(*eth_properties, "AutoNeg"); 144 eth_data.speed = extractProperty<unsigned int>(*eth_properties, "Speed"); 145 } 146 147 // Extract data that contains network config (HostName and DefaultGW) 148 const PropertiesMapType *config_properties = extractInterfaceProperties( 149 "/xyz/openbmc_project/network/config", 150 "xyz.openbmc_project.Network.SystemConfiguration", dbus_data); 151 152 if (config_properties != nullptr) { 153 eth_data.hostname = 154 extractProperty<std::string>(*config_properties, "HostName"); 155 eth_data.default_gateway = 156 extractProperty<std::string>(*config_properties, "DefaultGateway"); 157 } 158 } 159 160 // Helper function that changes bits netmask notation (i.e. /24) 161 // into full dot notation 162 inline std::string getNetmask(unsigned int bits) { 163 uint32_t value = 0xffffffff << (32 - bits); 164 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 165 std::to_string((value >> 16) & 0xff) + "." + 166 std::to_string((value >> 8) & 0xff) + "." + 167 std::to_string(value & 0xff); 168 return netmask; 169 } 170 171 // Helper function that extracts data for single ethernet ipv4 address 172 void extractIPv4Data(const std::string ðiface_id, 173 const GetManagedObjectsType &dbus_data, 174 std::vector<IPv4AddressData> &ipv4_config) { 175 // Since there might be several IPv4 configurations aligned with 176 // single ethernet interface, loop over all of them 177 for (auto &objpath : dbus_data) { 178 // Check if propper patter for object path appears 179 if (boost::starts_with( 180 objpath.first.value, 181 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/")) { 182 // and get approrpiate interface 183 const auto &interface = 184 objpath.second.find("xyz.openbmc_project.Network.IP"); 185 if (interface != objpath.second.end()) { 186 // Make a properties 'shortcut', to make everything more readable 187 const PropertiesMapType &properties = interface->second; 188 // Instance IPv4AddressData structure, and set as appropriate 189 IPv4AddressData ipv4_address; 190 // IPv4 address 191 ipv4_address.address = 192 extractProperty<std::string>(properties, "Address"); 193 // IPv4 gateway 194 ipv4_address.gateway = 195 extractProperty<std::string>(properties, "Gateway"); 196 197 // Origin is kind of DBus object so fetch pointer... 198 const std::string *origin = 199 extractProperty<std::string>(properties, "Origin"); 200 if (origin != nullptr) { 201 // ... and get everything after last dot 202 int last = origin->rfind("."); 203 if (last != std::string::npos) { 204 ipv4_address.origin = origin->substr(last + 1); 205 } 206 } 207 208 // Netmask is presented as PrefixLength 209 const auto *mask = 210 extractProperty<uint8_t>(properties, "PrefixLength"); 211 if (mask != nullptr) { 212 // convert it to the string 213 ipv4_address.netmask = getNetmask(*mask); 214 } 215 216 // Attach IPv4 only if address is present 217 if (ipv4_address.address != nullptr) { 218 // Check if given addres is local, or global 219 if (boost::starts_with(*ipv4_address.address, "169.254")) { 220 ipv4_address.global = false; 221 } else { 222 ipv4_address.global = true; 223 } 224 ipv4_config.emplace_back(std::move(ipv4_address)); 225 } 226 } 227 } 228 } 229 } 230 231 public: 232 /** 233 * Function that retrieves all properties for given Ethernet Interface Object 234 * from EntityManager Network Manager 235 * @param ethiface_id a eth interface id to query on DBus 236 * @param callback a function that shall be called to convert Dbus output into 237 * JSON 238 */ 239 template <typename CallbackFunc> 240 void getEthernetIfaceData(const std::string ðiface_id, 241 CallbackFunc &&callback) { 242 crow::connections::system_bus->async_method_call( 243 [ 244 this, ethiface_id{std::move(ethiface_id)}, 245 callback{std::move(callback)} 246 ](const boost::system::error_code error_code, 247 const GetManagedObjectsType &resp) { 248 249 EthernetInterfaceData eth_data{}; 250 std::vector<IPv4AddressData> ipv4_data; 251 ipv4_data.reserve(MAX_IPV4_ADDRESSES_PER_INTERFACE); 252 253 if (error_code) { 254 // Something wrong on DBus, the error_code is not important at this 255 // moment, just return success=false, and empty output. Since size 256 // of vector may vary depending on information from Network Manager, 257 // and empty output could not be treated same way as error. 258 callback(false, eth_data, ipv4_data); 259 return; 260 } 261 262 extractEthernetInterfaceData(ethiface_id, resp, eth_data); 263 extractIPv4Data(ethiface_id, resp, ipv4_data); 264 265 // Fix global GW 266 for (IPv4AddressData &ipv4 : ipv4_data) { 267 if ((ipv4.global) && 268 ((ipv4.gateway == nullptr) || (*ipv4.gateway == "0.0.0.0"))) { 269 ipv4.gateway = eth_data.default_gateway; 270 } 271 } 272 273 // Finally make a callback with usefull data 274 callback(true, eth_data, ipv4_data); 275 }, 276 {"xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 277 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"}); 278 }; 279 280 /** 281 * Function that retrieves all Ethernet Interfaces available through Network 282 * Manager 283 * @param callback a function that shall be called to convert Dbus output into 284 * JSON. 285 */ 286 template <typename CallbackFunc> 287 void getEthernetIfaceList(CallbackFunc &&callback) { 288 crow::connections::system_bus->async_method_call( 289 [ this, callback{std::move(callback)} ]( 290 const boost::system::error_code error_code, 291 const GetManagedObjectsType &resp) { 292 // Callback requires vector<string> to retrieve all available ethernet 293 // interfaces 294 std::vector<std::string> iface_list; 295 iface_list.reserve(resp.size()); 296 if (error_code) { 297 // Something wrong on DBus, the error_code is not important at this 298 // moment, just return success=false, and empty output. Since size 299 // of vector may vary depending on information from Network Manager, 300 // and empty output could not be treated same way as error. 301 callback(false, iface_list); 302 return; 303 } 304 305 // Iterate over all retrieved ObjectPaths. 306 for (auto &objpath : resp) { 307 // And all interfaces available for certain ObjectPath. 308 for (auto &interface : objpath.second) { 309 // If interface is xyz.openbmc_project.Network.EthernetInterface, 310 // this is what we're looking for. 311 if (interface.first == 312 "xyz.openbmc_project.Network.EthernetInterface") { 313 // Cut out everyting until last "/", ... 314 const std::string &iface_id = objpath.first.value; 315 std::size_t last_pos = iface_id.rfind("/"); 316 if (last_pos != std::string::npos) { 317 // and put it into output vector. 318 iface_list.emplace_back(iface_id.substr(last_pos + 1)); 319 } 320 } 321 } 322 } 323 // Finally make a callback with usefull data 324 callback(true, iface_list); 325 }, 326 {"xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 327 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"}); 328 }; 329 }; 330 331 /** 332 * EthernetCollection derived class for delivering Ethernet Collection Schema 333 */ 334 class EthernetCollection : public Node { 335 public: 336 template <typename CrowApp> 337 // TODO(Pawel) Remove line from below, where we assume that there is only one 338 // manager called openbmc This shall be generic, but requires to update 339 // GetSubroutes method 340 EthernetCollection(CrowApp &app) 341 : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/") { 342 Node::json["@odata.type"] = 343 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 344 Node::json["@odata.context"] = 345 "/redfish/v1/" 346 "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection"; 347 Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/EthernetInterfaces"; 348 Node::json["Name"] = "Ethernet Network Interface Collection"; 349 Node::json["Description"] = 350 "Collection of EthernetInterfaces for this Manager"; 351 352 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}}, 353 {crow::HTTPMethod::HEAD, {{"Login"}}}, 354 {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}}, 355 {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}}, 356 {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}}, 357 {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}}; 358 } 359 360 private: 361 /** 362 * Functions triggers appropriate requests on DBus 363 */ 364 void doGet(crow::response &res, const crow::request &req, 365 const std::vector<std::string> ¶ms) override { 366 // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for 367 // any Manager, not only hardcoded 'openbmc'. 368 std::string manager_id = "openbmc"; 369 370 // Get eth interface list, and call the below callback for JSON preparation 371 ethernet_provider.getEthernetIfaceList( 372 [&, manager_id{std::move(manager_id)} ]( 373 const bool &success, const std::vector<std::string> &iface_list) { 374 if (success) { 375 nlohmann::json iface_array = nlohmann::json::array(); 376 for (const std::string &iface_item : iface_list) { 377 iface_array.push_back( 378 {{"@odata.id", "/redfish/v1/Managers/" + manager_id + 379 "/EthernetInterfaces/" + iface_item}}); 380 } 381 Node::json["Members"] = iface_array; 382 Node::json["Members@odata.count"] = iface_array.size(); 383 Node::json["@odata.id"] = 384 "/redfish/v1/Managers/" + manager_id + "/EthernetInterfaces"; 385 res.json_value = Node::json; 386 } else { 387 // No success, best what we can do is return INTERNALL ERROR 388 res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR); 389 } 390 res.end(); 391 }); 392 } 393 394 // Ethernet Provider object 395 // TODO(Pawel) consider move it to singleton 396 OnDemandEthernetProvider ethernet_provider; 397 }; 398 399 /** 400 * EthernetInterface derived class for delivering Ethernet Schema 401 */ 402 class EthernetInterface : public Node { 403 public: 404 /* 405 * Default Constructor 406 */ 407 template <typename CrowApp> 408 // TODO(Pawel) Remove line from below, where we assume that there is only one 409 // manager called openbmc This shall be generic, but requires to update 410 // GetSubroutes method 411 EthernetInterface(CrowApp &app) 412 : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/", 413 std::string()) { 414 Node::json["@odata.type"] = "#EthernetInterface.v1_2_0.EthernetInterface"; 415 Node::json["@odata.context"] = 416 "/redfish/v1/$metadata#EthernetInterface.EthernetInterface"; 417 Node::json["Name"] = "Manager Ethernet Interface"; 418 Node::json["Description"] = "Management Network Interface"; 419 420 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}}, 421 {crow::HTTPMethod::HEAD, {{"Login"}}}, 422 {crow::HTTPMethod::PATCH, {{"ConfigureComponents"}}}, 423 {crow::HTTPMethod::PUT, {{"ConfigureComponents"}}}, 424 {crow::HTTPMethod::DELETE, {{"ConfigureComponents"}}}, 425 {crow::HTTPMethod::POST, {{"ConfigureComponents"}}}}; 426 } 427 428 private: 429 /** 430 * Functions triggers appropriate requests on DBus 431 */ 432 void doGet(crow::response &res, const crow::request &req, 433 const std::vector<std::string> ¶ms) override { 434 // TODO(Pawel) this shall be parametrized call (two params) to get 435 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 436 // Check if there is required param, truly entering this shall be 437 // impossible. 438 if (params.size() != 1) { 439 res.code = static_cast<int>(HttpRespCode::INTERNAL_ERROR); 440 res.end(); 441 return; 442 } 443 444 const std::string &iface_id = params[0]; 445 446 // Get single eth interface data, and call the below callback for JSON 447 // preparation 448 ethernet_provider.getEthernetIfaceData( 449 iface_id, [&, iface_id](const bool &success, 450 const EthernetInterfaceData ð_data, 451 const std::vector<IPv4AddressData> &ipv4_data) { 452 if (success) { 453 // Copy JSON object to avoid race condition 454 nlohmann::json json_response(Node::json); 455 456 // Fill out obvious data... 457 json_response["Id"] = iface_id; 458 json_response["@odata.id"] = 459 "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id; 460 461 // ... then the one from DBus, regarding eth iface... 462 if (eth_data.speed != nullptr) 463 json_response["SpeedMbps"] = *eth_data.speed; 464 465 if (eth_data.mac_address != nullptr) 466 json_response["MACAddress"] = *eth_data.mac_address; 467 468 if (eth_data.hostname != nullptr) 469 json_response["HostName"] = *eth_data.hostname; 470 471 if (eth_data.vlan_id != nullptr) { 472 json_response["VLAN"]["VLANEnable"] = true; 473 json_response["VLAN"]["VLANId"] = *eth_data.vlan_id; 474 } 475 476 // ... at last, check if there are IPv4 data and prepare appropriate 477 // collection 478 if (ipv4_data.size() > 0) { 479 nlohmann::json ipv4_array = nlohmann::json::array(); 480 for (auto &ipv4_config : ipv4_data) { 481 nlohmann::json json_ipv4; 482 if (ipv4_config.address != nullptr) { 483 json_ipv4["Address"] = *ipv4_config.address; 484 if (ipv4_config.gateway != nullptr) 485 json_ipv4["Gateway"] = *ipv4_config.gateway; 486 487 json_ipv4["AddressOrigin"] = ipv4_config.origin; 488 json_ipv4["SubnetMask"] = ipv4_config.netmask; 489 490 ipv4_array.push_back(json_ipv4); 491 } 492 } 493 json_response["IPv4Addresses"] = ipv4_array; 494 } 495 res.json_value = std::move(json_response); 496 } else { 497 // ... otherwise return error 498 // TODO(Pawel)consider distinguish between non existing object, and 499 // other errors 500 res.code = static_cast<int>(HttpRespCode::NOT_FOUND); 501 } 502 res.end(); 503 }); 504 } 505 506 // Ethernet Provider object 507 // TODO(Pawel) consider move it to singleton 508 OnDemandEthernetProvider ethernet_provider; 509 }; 510 511 } // namespace redfish 512