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 <dbus_singleton.hpp> 19 #include <error_messages.hpp> 20 #include <node.hpp> 21 #include <utils/json_utils.hpp> 22 #include <boost/container/flat_map.hpp> 23 24 namespace redfish { 25 26 /** 27 * DBus types primitives for several generic DBus interfaces 28 * TODO(Pawel) consider move this to separate file into boost::dbus 29 */ 30 using PropertiesMapType = boost::container::flat_map< 31 std::string, 32 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t, 33 int32_t, uint32_t, int64_t, uint64_t, double>>; 34 35 using GetManagedObjectsType = boost::container::flat_map< 36 sdbusplus::message::object_path, 37 boost::container::flat_map< 38 std::string, 39 boost::container::flat_map< 40 std::string, sdbusplus::message::variant< 41 std::string, bool, uint8_t, int16_t, uint16_t, 42 int32_t, uint32_t, int64_t, uint64_t, double>>>>; 43 44 /** 45 * Structure for keeping IPv4 data required by Redfish 46 * TODO(Pawel) consider change everything to ptr, or to non-ptr values. 47 */ 48 struct IPv4AddressData { 49 std::string id; 50 const std::string *address; 51 const std::string *domain; 52 const std::string *gateway; 53 std::string netmask; 54 std::string origin; 55 bool global; 56 /** 57 * @brief Operator< to enable sorting 58 * 59 * @param[in] obj Object to compare with 60 * 61 * @return This object id < supplied object id 62 */ 63 bool operator<(const IPv4AddressData &obj) const { return (id < obj.id); } 64 }; 65 66 /** 67 * Structure for keeping basic single Ethernet Interface information 68 * available from DBus 69 */ 70 struct EthernetInterfaceData { 71 const unsigned int *speed; 72 const bool *auto_neg; 73 const std::string *hostname; 74 const std::string *default_gateway; 75 const std::string *mac_address; 76 const unsigned int *vlan_id; 77 }; 78 79 /** 80 * OnDemandEthernetProvider 81 * Ethernet provider class that retrieves data directly from dbus, before 82 * setting it into JSON output. This does not cache any data. 83 * 84 * TODO(Pawel) 85 * This perhaps shall be different file, which has to be chosen on compile time 86 * depending on OEM needs 87 */ 88 class OnDemandEthernetProvider { 89 private: 90 // Consts that may have influence on EthernetProvider performance/memory usage 91 const size_t MAX_IPV4_ADDRESSES_PER_INTERFACE = 10; 92 93 // Helper function that allows to extract GetAllPropertiesType from 94 // GetManagedObjectsType, based on object path, and interface name 95 const PropertiesMapType *extractInterfaceProperties( 96 const sdbusplus::message::object_path &objpath, 97 const std::string &interface, const GetManagedObjectsType &dbus_data) { 98 const auto &dbus_obj = dbus_data.find(objpath); 99 if (dbus_obj != dbus_data.end()) { 100 const auto &iface = dbus_obj->second.find(interface); 101 if (iface != dbus_obj->second.end()) { 102 return &iface->second; 103 } 104 } 105 return nullptr; 106 } 107 108 // Helper Wrapper that does inline object_path conversion from string 109 // into sdbusplus::message::object_path type 110 inline const PropertiesMapType *extractInterfaceProperties( 111 const std::string &objpath, const std::string &interface, 112 const GetManagedObjectsType &dbus_data) { 113 const auto &dbus_obj = sdbusplus::message::object_path{objpath}; 114 return extractInterfaceProperties(dbus_obj, interface, dbus_data); 115 } 116 117 // Helper function that allows to get pointer to the property from 118 // GetAllPropertiesType native, or extracted by GetAllPropertiesType 119 template <typename T> 120 inline T const *const extractProperty(const PropertiesMapType &properties, 121 const std::string &name) { 122 const auto &property = properties.find(name); 123 if (property != properties.end()) { 124 return mapbox::get_ptr<const T>(property->second); 125 } 126 return nullptr; 127 } 128 // TODO(Pawel) Consider to move the above functions to dbus 129 // generic_interfaces.hpp 130 131 // Helper function that extracts data from several dbus objects and several 132 // interfaces required by single ethernet interface instance 133 void extractEthernetInterfaceData(const std::string ðiface_id, 134 const GetManagedObjectsType &dbus_data, 135 EthernetInterfaceData ð_data) { 136 // Extract data that contains MAC Address 137 const PropertiesMapType *mac_properties = extractInterfaceProperties( 138 "/xyz/openbmc_project/network/" + ethiface_id, 139 "xyz.openbmc_project.Network.MACAddress", dbus_data); 140 141 if (mac_properties != nullptr) { 142 eth_data.mac_address = 143 extractProperty<std::string>(*mac_properties, "MACAddress"); 144 } 145 146 const PropertiesMapType *vlan_properties = extractInterfaceProperties( 147 "/xyz/openbmc_project/network/" + ethiface_id, 148 "xyz.openbmc_project.Network.VLAN", dbus_data); 149 150 if (vlan_properties != nullptr) { 151 eth_data.vlan_id = extractProperty<unsigned int>(*vlan_properties, "Id"); 152 } 153 154 // Extract data that contains link information (auto negotiation and speed) 155 const PropertiesMapType *eth_properties = extractInterfaceProperties( 156 "/xyz/openbmc_project/network/" + ethiface_id, 157 "xyz.openbmc_project.Network.EthernetInterface", dbus_data); 158 159 if (eth_properties != nullptr) { 160 eth_data.auto_neg = extractProperty<bool>(*eth_properties, "AutoNeg"); 161 eth_data.speed = extractProperty<unsigned int>(*eth_properties, "Speed"); 162 } 163 164 // Extract data that contains network config (HostName and DefaultGW) 165 const PropertiesMapType *config_properties = extractInterfaceProperties( 166 "/xyz/openbmc_project/network/config", 167 "xyz.openbmc_project.Network.SystemConfiguration", dbus_data); 168 169 if (config_properties != nullptr) { 170 eth_data.hostname = 171 extractProperty<std::string>(*config_properties, "HostName"); 172 eth_data.default_gateway = 173 extractProperty<std::string>(*config_properties, "DefaultGateway"); 174 } 175 } 176 177 // Helper function that changes bits netmask notation (i.e. /24) 178 // into full dot notation 179 inline std::string getNetmask(unsigned int bits) { 180 uint32_t value = 0xffffffff << (32 - bits); 181 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 182 std::to_string((value >> 16) & 0xff) + "." + 183 std::to_string((value >> 8) & 0xff) + "." + 184 std::to_string(value & 0xff); 185 return netmask; 186 } 187 188 // Helper function that extracts data for single ethernet ipv4 address 189 void extractIPv4Data(const std::string ðiface_id, 190 const GetManagedObjectsType &dbus_data, 191 std::vector<IPv4AddressData> &ipv4_config) { 192 const std::string pathStart = 193 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/"; 194 195 // Since there might be several IPv4 configurations aligned with 196 // single ethernet interface, loop over all of them 197 for (auto &objpath : dbus_data) { 198 // Check if proper patter for object path appears 199 if (boost::starts_with( 200 static_cast<const std::string &>(objpath.first), 201 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/")) { 202 // and get approrpiate interface 203 const auto &interface = 204 objpath.second.find("xyz.openbmc_project.Network.IP"); 205 if (interface != objpath.second.end()) { 206 // Make a properties 'shortcut', to make everything more readable 207 const PropertiesMapType &properties = interface->second; 208 // Instance IPv4AddressData structure, and set as appropriate 209 IPv4AddressData ipv4_address; 210 211 ipv4_address.id = static_cast<const std::string &>(objpath.first) 212 .substr(pathStart.size()); 213 214 // IPv4 address 215 ipv4_address.address = 216 extractProperty<std::string>(properties, "Address"); 217 // IPv4 gateway 218 ipv4_address.gateway = 219 extractProperty<std::string>(properties, "Gateway"); 220 221 // Origin is kind of DBus object so fetch pointer... 222 const std::string *origin = 223 extractProperty<std::string>(properties, "Origin"); 224 if (origin != nullptr) { 225 ipv4_address.origin = 226 translateAddressOriginBetweenDBusAndRedfish(origin, true, true); 227 } 228 229 // Netmask is presented as PrefixLength 230 const auto *mask = 231 extractProperty<uint8_t>(properties, "PrefixLength"); 232 if (mask != nullptr) { 233 // convert it to the string 234 ipv4_address.netmask = getNetmask(*mask); 235 } 236 237 // Attach IPv4 only if address is present 238 if (ipv4_address.address != nullptr) { 239 // Check if given address is local, or global 240 if (boost::starts_with(*ipv4_address.address, "169.254")) { 241 ipv4_address.global = false; 242 } else { 243 ipv4_address.global = true; 244 } 245 ipv4_config.emplace_back(std::move(ipv4_address)); 246 } 247 } 248 } 249 } 250 251 /** 252 * We have to sort this vector and ensure that order of IPv4 addresses 253 * is consistent between GETs to allow modification and deletion in PATCHes 254 */ 255 std::sort(ipv4_config.begin(), ipv4_config.end()); 256 } 257 258 static const constexpr int ipV4AddressSectionsCount = 4; 259 260 public: 261 /** 262 * @brief Creates VLAN for given interface with given Id through D-Bus 263 * 264 * @param[in] ifaceId Id of interface for which VLAN will be created 265 * @param[in] inputVlanId ID of the new VLAN 266 * @param[in] callback Function that will be called after the operation 267 * 268 * @return None. 269 */ 270 template <typename CallbackFunc> 271 void createVlan(const std::string &ifaceId, const uint64_t &inputVlanId, 272 CallbackFunc &&callback) { 273 crow::connections::system_bus->async_method_call( 274 callback, "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 275 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", ifaceId, 276 static_cast<uint32_t>(inputVlanId)); 277 }; 278 279 /** 280 * @brief Sets given Id on the given VLAN interface through D-Bus 281 * 282 * @param[in] ifaceId Id of VLAN interface that should be modified 283 * @param[in] inputVlanId New ID of the VLAN 284 * @param[in] callback Function that will be called after the operation 285 * 286 * @return None. 287 */ 288 template <typename CallbackFunc> 289 static void changeVlanId(const std::string &ifaceId, 290 const uint32_t &inputVlanId, 291 CallbackFunc &&callback) { 292 crow::connections::system_bus->async_method_call( 293 callback, "xyz.openbmc_project.Network", 294 std::string("/xyz/openbmc_project/network/") + ifaceId, 295 "org.freedesktop.DBus.Properties", "Set", 296 "xyz.openbmc_project.Network.VLAN", "Id", 297 sdbusplus::message::variant<uint32_t>(inputVlanId)); 298 }; 299 300 /** 301 * @brief Helper function that verifies IP address to check if it is in 302 * proper format. If bits pointer is provided, also calculates active 303 * bit count for Subnet Mask. 304 * 305 * @param[in] ip IP that will be verified 306 * @param[out] bits Calculated mask in bits notation 307 * 308 * @return true in case of success, false otherwise 309 */ 310 bool ipv4VerifyIpAndGetBitcount(const std::string &ip, 311 uint8_t *bits = nullptr) { 312 std::vector<std::string> bytesInMask; 313 314 boost::split(bytesInMask, ip, boost::is_any_of(".")); 315 316 if (bytesInMask.size() != ipV4AddressSectionsCount) { 317 return false; 318 } 319 320 if (bits != nullptr) { 321 *bits = 0; 322 } 323 324 char *endPtr; 325 long previousValue = 255; 326 bool firstZeroInByteHit; 327 for (uint8_t byteIdx = 0; byteIdx < ipV4AddressSectionsCount; byteIdx++) { 328 // Use strtol instead of stroi to avoid exceptions 329 long value = std::strtol(bytesInMask[byteIdx].c_str(), &endPtr, 10); 330 331 // endPtr should point to the end of the string, otherwise given string 332 // is not 100% number 333 if (*endPtr != '\0') { 334 return false; 335 } 336 337 // Value should be contained in byte 338 if (value < 0 || value > 255) { 339 return false; 340 } 341 342 if (bits != nullptr) { 343 // Mask has to be continuous between bytes 344 if (previousValue != 255 && value != 0) { 345 return false; 346 } 347 348 // Mask has to be continuous inside bytes 349 firstZeroInByteHit = false; 350 351 // Count bits 352 for (int bitIdx = 7; bitIdx >= 0; bitIdx--) { 353 if (value & (1 << bitIdx)) { 354 if (firstZeroInByteHit) { 355 // Continuity not preserved 356 return false; 357 } else { 358 (*bits)++; 359 } 360 } else { 361 firstZeroInByteHit = true; 362 } 363 } 364 } 365 366 previousValue = value; 367 } 368 369 return true; 370 } 371 372 /** 373 * @brief Changes IPv4 address type property (Address, Gateway) 374 * 375 * @param[in] ifaceId Id of interface whose IP should be modified 376 * @param[in] ipIdx Index of IP in input array that should be modified 377 * @param[in] ipHash DBus Hash id of modified IP 378 * @param[in] name Name of field in JSON representation 379 * @param[in] newValue New value that should be written 380 * @param[io] asyncResp Response object that will be returned to client 381 * 382 * @return true if give IP is valid and has been sent do D-Bus, false 383 * otherwise 384 */ 385 void changeIPv4AddressProperty(const std::string &ifaceId, int ipIdx, 386 const std::string &ipHash, 387 const std::string &name, 388 const std::string &newValue, 389 const std::shared_ptr<AsyncResp> &asyncResp) { 390 auto callback = [ 391 asyncResp, ipIdx{std::move(ipIdx)}, name{std::move(name)}, 392 newValue{std::move(newValue)} 393 ](const boost::system::error_code ec) { 394 if (ec) { 395 messages::addMessageToJson( 396 asyncResp->res.json_value, messages::internalError(), 397 "/IPv4Addresses/" + std::to_string(ipIdx) + "/" + name); 398 } else { 399 asyncResp->res.json_value["IPv4Addresses"][ipIdx][name] = newValue; 400 } 401 }; 402 403 crow::connections::system_bus->async_method_call( 404 std::move(callback), "xyz.openbmc_project.Network", 405 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 406 "org.freedesktop.DBus.Properties", "Set", 407 "xyz.openbmc_project.Network.IP", name, 408 sdbusplus::message::variant<std::string>(newValue)); 409 }; 410 411 /** 412 * @brief Changes IPv4 address origin property 413 * 414 * @param[in] ifaceId Id of interface whose IP should be modified 415 * @param[in] ipIdx Index of IP in input array that should be modified 416 * @param[in] ipHash DBus Hash id of modified IP 417 * @param[in] newValue New value in Redfish format 418 * @param[in] newValueDbus New value in D-Bus format 419 * @param[io] asyncResp Response object that will be returned to client 420 * 421 * @return true if give IP is valid and has been sent do D-Bus, false 422 * otherwise 423 */ 424 void changeIPv4Origin(const std::string &ifaceId, int ipIdx, 425 const std::string &ipHash, const std::string &newValue, 426 const std::string &newValueDbus, 427 const std::shared_ptr<AsyncResp> &asyncResp) { 428 auto callback = 429 [ asyncResp, ipIdx{std::move(ipIdx)}, 430 newValue{std::move(newValue)} ](const boost::system::error_code ec) { 431 if (ec) { 432 messages::addMessageToJson( 433 asyncResp->res.json_value, messages::internalError(), 434 "/IPv4Addresses/" + std::to_string(ipIdx) + "/AddressOrigin"); 435 } else { 436 asyncResp->res.json_value["IPv4Addresses"][ipIdx]["AddressOrigin"] = 437 newValue; 438 } 439 }; 440 441 crow::connections::system_bus->async_method_call( 442 std::move(callback), "xyz.openbmc_project.Network", 443 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 444 "org.freedesktop.DBus.Properties", "Set", 445 "xyz.openbmc_project.Network.IP", "Origin", 446 sdbusplus::message::variant<std::string>(newValueDbus)); 447 }; 448 449 /** 450 * @brief Modifies SubnetMask for given IP 451 * 452 * @param[in] ifaceId Id of interface whose IP should be modified 453 * @param[in] ipIdx Index of IP in input array that should be modified 454 * @param[in] ipHash DBus Hash id of modified IP 455 * @param[in] newValueStr Mask in dot notation as string 456 * @param[in] newValue Mask as PrefixLength in bitcount 457 * @param[io] asyncResp Response object that will be returned to client 458 * 459 * @return None 460 */ 461 void changeIPv4SubnetMaskProperty( 462 const std::string &ifaceId, int ipIdx, const std::string &ipHash, 463 const std::string &newValueStr, uint8_t &newValue, 464 const std::shared_ptr<AsyncResp> &asyncResp) { 465 auto callback = [ 466 asyncResp, ipIdx{std::move(ipIdx)}, newValueStr{std::move(newValueStr)} 467 ](const boost::system::error_code ec) { 468 if (ec) { 469 messages::addMessageToJson( 470 asyncResp->res.json_value, messages::internalError(), 471 "/IPv4Addresses/" + std::to_string(ipIdx) + "/SubnetMask"); 472 } else { 473 asyncResp->res.json_value["IPv4Addresses"][ipIdx]["SubnetMask"] = 474 newValueStr; 475 } 476 }; 477 478 crow::connections::system_bus->async_method_call( 479 std::move(callback), "xyz.openbmc_project.Network", 480 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 481 "org.freedesktop.DBus.Properties", "Set", 482 "xyz.openbmc_project.Network.IP", "PrefixLength", 483 sdbusplus::message::variant<uint8_t>(newValue)); 484 }; 485 486 /** 487 * @brief Disables VLAN with given ifaceId 488 * 489 * @param[in] ifaceId Id of VLAN interface that should be disabled 490 * @param[in] callback Function that will be called after the operation 491 * 492 * @return None. 493 */ 494 template <typename CallbackFunc> 495 static void disableVlan(const std::string &ifaceId, CallbackFunc &&callback) { 496 crow::connections::system_bus->async_method_call( 497 callback, "xyz.openbmc_project.Network", 498 std::string("/xyz/openbmc_project/network/") + ifaceId, 499 "xyz.openbmc_project.Object.Delete", "Delete"); 500 }; 501 502 /** 503 * @brief Sets given HostName of the machine through D-Bus 504 * 505 * @param[in] newHostname New name that HostName will be changed to 506 * @param[in] callback Function that will be called after the operation 507 * 508 * @return None. 509 */ 510 template <typename CallbackFunc> 511 void setHostName(const std::string &newHostname, CallbackFunc &&callback) { 512 crow::connections::system_bus->async_method_call( 513 callback, "xyz.openbmc_project.Network", 514 "/xyz/openbmc_project/network/config", 515 "org.freedesktop.DBus.Properties", "Set", 516 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 517 sdbusplus::message::variant<std::string>(newHostname)); 518 }; 519 520 /** 521 * @brief Deletes given IPv4 522 * 523 * @param[in] ifaceId Id of interface whose IP should be deleted 524 * @param[in] ipIdx Index of IP in input array that should be deleted 525 * @param[in] ipHash DBus Hash id of IP that should be deleted 526 * @param[io] asyncResp Response object that will be returned to client 527 * 528 * @return None 529 */ 530 void deleteIPv4(const std::string &ifaceId, const std::string &ipHash, 531 unsigned int ipIdx, 532 const std::shared_ptr<AsyncResp> &asyncResp) { 533 crow::connections::system_bus->async_method_call( 534 [ ipIdx{std::move(ipIdx)}, asyncResp{std::move(asyncResp)} ]( 535 const boost::system::error_code ec) { 536 if (ec) { 537 messages::addMessageToJson( 538 asyncResp->res.json_value, messages::internalError(), 539 "/IPv4Addresses/" + std::to_string(ipIdx) + "/"); 540 } else { 541 asyncResp->res.json_value["IPv4Addresses"][ipIdx] = nullptr; 542 } 543 }, 544 "xyz.openbmc_project.Network", 545 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 546 "xyz.openbmc_project.Object.Delete", "Delete"); 547 } 548 549 /** 550 * @brief Creates IPv4 with given data 551 * 552 * @param[in] ifaceId Id of interface whose IP should be deleted 553 * @param[in] ipIdx Index of IP in input array that should be deleted 554 * @param[in] ipHash DBus Hash id of IP that should be deleted 555 * @param[io] asyncResp Response object that will be returned to client 556 * 557 * @return None 558 */ 559 void createIPv4(const std::string &ifaceId, unsigned int ipIdx, 560 uint8_t subnetMask, const std::string &gateway, 561 const std::string &address, 562 const std::shared_ptr<AsyncResp> &asyncResp) { 563 auto createIpHandler = [ 564 ipIdx{std::move(ipIdx)}, asyncResp{std::move(asyncResp)} 565 ](const boost::system::error_code ec) { 566 if (ec) { 567 messages::addMessageToJson( 568 asyncResp->res.json_value, messages::internalError(), 569 "/IPv4Addresses/" + std::to_string(ipIdx) + "/"); 570 } 571 }; 572 573 crow::connections::system_bus->async_method_call( 574 std::move(createIpHandler), "xyz.openbmc_project.Network", 575 "/xyz/openbmc_project/network/" + ifaceId, 576 "xyz.openbmc_project.Network.IP.Create", "IP", 577 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, subnetMask, 578 gateway); 579 } 580 581 /** 582 * @brief Translates Address Origin value from D-Bus to Redfish format and 583 * vice-versa 584 * 585 * @param[in] inputOrigin Input value that should be translated 586 * @param[in] isIPv4 True for IPv4 origins, False for IPv6 587 * @param[in] isFromDBus True for DBus->Redfish conversion, false for reverse 588 * 589 * @return Empty string in case of failure, translated value otherwise 590 */ 591 std::string translateAddressOriginBetweenDBusAndRedfish( 592 const std::string *inputOrigin, bool isIPv4, bool isFromDBus) { 593 // Invalid pointer 594 if (inputOrigin == nullptr) { 595 return ""; 596 } 597 598 static const constexpr unsigned int firstIPv4OnlyIdx = 1; 599 static const constexpr unsigned int firstIPv6OnlyIdx = 3; 600 601 std::array<std::pair<const char *, const char *>, 6> translationTable{ 602 {{"xyz.openbmc_project.Network.IP.AddressOrigin.Static", "Static"}, 603 {"xyz.openbmc_project.Network.IP.AddressOrigin.DHCP", "DHCP"}, 604 {"xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal", 605 "IPv4LinkLocal"}, 606 {"xyz.openbmc_project.Network.IP.AddressOrigin.DHCP", "DHCPv6"}, 607 {"xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal", 608 "LinkLocal"}, 609 {"xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC", "SLAAC"}}}; 610 611 for (unsigned int i = 0; i < translationTable.size(); i++) { 612 // Skip unrelated 613 if (isIPv4 && i >= firstIPv6OnlyIdx) break; 614 if (!isIPv4 && i >= firstIPv4OnlyIdx && i < firstIPv6OnlyIdx) continue; 615 616 // When translating D-Bus to Redfish compare input to first element 617 if (isFromDBus && translationTable[i].first == *inputOrigin) 618 return translationTable[i].second; 619 620 // When translating Redfish to D-Bus compare input to second element 621 if (!isFromDBus && translationTable[i].second == *inputOrigin) 622 return translationTable[i].first; 623 } 624 625 // If we are still here, that means that value has not been found 626 return ""; 627 } 628 629 /** 630 * Function that retrieves all properties for given Ethernet Interface 631 * Object 632 * from EntityManager Network Manager 633 * @param ethiface_id a eth interface id to query on DBus 634 * @param callback a function that shall be called to convert Dbus output 635 * into JSON 636 */ 637 template <typename CallbackFunc> 638 void getEthernetIfaceData(const std::string ðiface_id, 639 CallbackFunc &&callback) { 640 crow::connections::system_bus->async_method_call( 641 [ 642 this, ethiface_id{std::move(ethiface_id)}, 643 callback{std::move(callback)} 644 ](const boost::system::error_code error_code, 645 const GetManagedObjectsType &resp) { 646 647 EthernetInterfaceData eth_data{}; 648 std::vector<IPv4AddressData> ipv4_data; 649 ipv4_data.reserve(MAX_IPV4_ADDRESSES_PER_INTERFACE); 650 651 if (error_code) { 652 // Something wrong on DBus, the error_code is not important at 653 // this moment, just return success=false, and empty output. Since 654 // size of vector may vary depending on information from Network 655 // Manager, and empty output could not be treated same way as 656 // error. 657 callback(false, eth_data, ipv4_data); 658 return; 659 } 660 661 extractEthernetInterfaceData(ethiface_id, resp, eth_data); 662 extractIPv4Data(ethiface_id, resp, ipv4_data); 663 664 // Fix global GW 665 for (IPv4AddressData &ipv4 : ipv4_data) { 666 if ((ipv4.global) && 667 ((ipv4.gateway == nullptr) || (*ipv4.gateway == "0.0.0.0"))) { 668 ipv4.gateway = eth_data.default_gateway; 669 } 670 } 671 672 // Finally make a callback with usefull data 673 callback(true, eth_data, ipv4_data); 674 }, 675 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 676 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 677 }; 678 679 /** 680 * Function that retrieves all Ethernet Interfaces available through Network 681 * Manager 682 * @param callback a function that shall be called to convert Dbus output into 683 * JSON. 684 */ 685 template <typename CallbackFunc> 686 void getEthernetIfaceList(CallbackFunc &&callback) { 687 crow::connections::system_bus->async_method_call( 688 [ this, callback{std::move(callback)} ]( 689 const boost::system::error_code error_code, 690 GetManagedObjectsType &resp) { 691 // Callback requires vector<string> to retrieve all available ethernet 692 // interfaces 693 std::vector<std::string> iface_list; 694 iface_list.reserve(resp.size()); 695 if (error_code) { 696 // Something wrong on DBus, the error_code is not important at this 697 // moment, just return success=false, and empty output. Since size 698 // of vector may vary depending on information from Network Manager, 699 // and empty output could not be treated same way as error. 700 callback(false, iface_list); 701 return; 702 } 703 704 // Iterate over all retrieved ObjectPaths. 705 for (auto &objpath : resp) { 706 // And all interfaces available for certain ObjectPath. 707 for (auto &interface : objpath.second) { 708 // If interface is xyz.openbmc_project.Network.EthernetInterface, 709 // this is what we're looking for. 710 if (interface.first == 711 "xyz.openbmc_project.Network.EthernetInterface") { 712 // Cut out everyting until last "/", ... 713 const std::string &iface_id = 714 static_cast<const std::string &>(objpath.first); 715 std::size_t last_pos = iface_id.rfind("/"); 716 if (last_pos != std::string::npos) { 717 // and put it into output vector. 718 iface_list.emplace_back(iface_id.substr(last_pos + 1)); 719 } 720 } 721 } 722 } 723 // Finally make a callback with useful data 724 callback(true, iface_list); 725 }, 726 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 727 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 728 }; 729 }; 730 731 /** 732 * EthernetCollection derived class for delivering Ethernet Collection Schema 733 */ 734 class EthernetCollection : public Node { 735 public: 736 template <typename CrowApp> 737 // TODO(Pawel) Remove line from below, where we assume that there is only one 738 // manager called openbmc This shall be generic, but requires to update 739 // GetSubroutes method 740 EthernetCollection(CrowApp &app) 741 : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/") { 742 Node::json["@odata.type"] = 743 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 744 Node::json["@odata.context"] = 745 "/redfish/v1/" 746 "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection"; 747 Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/EthernetInterfaces"; 748 Node::json["Name"] = "Ethernet Network Interface Collection"; 749 Node::json["Description"] = 750 "Collection of EthernetInterfaces for this Manager"; 751 752 entityPrivileges = { 753 {boost::beast::http::verb::get, {{"Login"}}}, 754 {boost::beast::http::verb::head, {{"Login"}}}, 755 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 756 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 757 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 758 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 759 } 760 761 private: 762 /** 763 * Functions triggers appropriate requests on DBus 764 */ 765 void doGet(crow::response &res, const crow::request &req, 766 const std::vector<std::string> ¶ms) override { 767 // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for 768 // any Manager, not only hardcoded 'openbmc'. 769 std::string manager_id = "openbmc"; 770 771 // Get eth interface list, and call the below callback for JSON preparation 772 ethernet_provider.getEthernetIfaceList( 773 [&, manager_id{std::move(manager_id)} ]( 774 const bool &success, const std::vector<std::string> &iface_list) { 775 if (success) { 776 nlohmann::json iface_array = nlohmann::json::array(); 777 for (const std::string &iface_item : iface_list) { 778 iface_array.push_back( 779 {{"@odata.id", "/redfish/v1/Managers/" + manager_id + 780 "/EthernetInterfaces/" + iface_item}}); 781 } 782 Node::json["Members"] = iface_array; 783 Node::json["Members@odata.count"] = iface_array.size(); 784 Node::json["@odata.id"] = 785 "/redfish/v1/Managers/" + manager_id + "/EthernetInterfaces"; 786 res.json_value = Node::json; 787 } else { 788 // No success, best what we can do is return INTERNALL ERROR 789 res.result(boost::beast::http::status::internal_server_error); 790 } 791 res.end(); 792 }); 793 } 794 795 // Ethernet Provider object 796 // TODO(Pawel) consider move it to singleton 797 OnDemandEthernetProvider ethernet_provider; 798 }; 799 800 /** 801 * EthernetInterface derived class for delivering Ethernet Schema 802 */ 803 class EthernetInterface : public Node { 804 public: 805 /* 806 * Default Constructor 807 */ 808 template <typename CrowApp> 809 // TODO(Pawel) Remove line from below, where we assume that there is only one 810 // manager called openbmc This shall be generic, but requires to update 811 // GetSubroutes method 812 EthernetInterface(CrowApp &app) 813 : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/", 814 std::string()) { 815 Node::json["@odata.type"] = "#EthernetInterface.v1_2_0.EthernetInterface"; 816 Node::json["@odata.context"] = 817 "/redfish/v1/$metadata#EthernetInterface.EthernetInterface"; 818 Node::json["Name"] = "Manager Ethernet Interface"; 819 Node::json["Description"] = "Management Network Interface"; 820 821 entityPrivileges = { 822 {boost::beast::http::verb::get, {{"Login"}}}, 823 {boost::beast::http::verb::head, {{"Login"}}}, 824 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 825 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 826 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 827 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 828 } 829 830 // TODO(kkowalsk) Find a suitable class/namespace for this 831 static void handleVlanPatch(const std::string &ifaceId, 832 const nlohmann::json &input, 833 const EthernetInterfaceData ð_data, 834 const std::string &pathPrefix, 835 const std::shared_ptr<AsyncResp> &asyncResp) { 836 if (!input.is_object()) { 837 messages::addMessageToJson( 838 asyncResp->res.json_value, 839 messages::propertyValueTypeError(input.dump(), "VLAN"), pathPrefix); 840 return; 841 } 842 843 const std::string pathStart = (pathPrefix == "/") ? "" : pathPrefix; 844 nlohmann::json ¶msJson = 845 (pathPrefix == "/") 846 ? asyncResp->res.json_value 847 : asyncResp->res.json_value[nlohmann::json_pointer<nlohmann::json>( 848 pathPrefix)]; 849 bool inputVlanEnabled; 850 uint64_t inputVlanId; 851 852 json_util::Result inputVlanEnabledState = json_util::getBool( 853 "VLANEnable", input, inputVlanEnabled, 854 static_cast<int>(json_util::MessageSetting::TYPE_ERROR), 855 asyncResp->res.json_value, std::string(pathStart + "/VLANEnable")); 856 json_util::Result inputVlanIdState = json_util::getUnsigned( 857 "VLANId", input, inputVlanId, 858 static_cast<int>(json_util::MessageSetting::TYPE_ERROR), 859 asyncResp->res.json_value, std::string(pathStart + "/VLANId")); 860 bool inputInvalid = false; 861 862 // Do not proceed if fields in VLAN object were of wrong type 863 if (inputVlanEnabledState == json_util::Result::WRONG_TYPE || 864 inputVlanIdState == json_util::Result::WRONG_TYPE) { 865 return; 866 } 867 868 // Verify input 869 if (eth_data.vlan_id == nullptr) { 870 // This interface is not a VLAN. Cannot do anything with it 871 // TODO(kkowalsk) Change this message 872 messages::addMessageToJson(asyncResp->res.json_value, 873 messages::propertyMissing("VLANEnable"), 874 pathPrefix); 875 876 inputInvalid = true; 877 } else { 878 // Load actual data into field values if they were not provided 879 if (inputVlanEnabledState == json_util::Result::NOT_EXIST) { 880 inputVlanEnabled = true; 881 } 882 883 if (inputVlanIdState == json_util::Result::NOT_EXIST) { 884 inputVlanId = *eth_data.vlan_id; 885 } 886 } 887 888 // Do not proceed if input has not been valid 889 if (inputInvalid) { 890 return; 891 } 892 893 // VLAN is configured on the interface 894 if (inputVlanEnabled == true && inputVlanId != *eth_data.vlan_id) { 895 // Change VLAN Id 896 paramsJson["VLANId"] = inputVlanId; 897 OnDemandEthernetProvider::changeVlanId( 898 ifaceId, static_cast<uint32_t>(inputVlanId), 899 [&, asyncResp, pathPrefx{std::move(pathPrefix)} ]( 900 const boost::system::error_code ec) { 901 if (ec) { 902 messages::addMessageToJson(asyncResp->res.json_value, 903 messages::internalError(), pathPrefix); 904 } else { 905 paramsJson["VLANEnable"] = true; 906 } 907 }); 908 } else if (inputVlanEnabled == false) { 909 // Disable VLAN 910 OnDemandEthernetProvider::disableVlan( 911 ifaceId, [&, asyncResp, pathPrefx{std::move(pathPrefix)} ]( 912 const boost::system::error_code ec) { 913 if (ec) { 914 messages::addMessageToJson(asyncResp->res.json_value, 915 messages::internalError(), pathPrefix); 916 } else { 917 paramsJson["VLANEnable"] = false; 918 } 919 }); 920 } 921 } 922 923 private: 924 void handleHostnamePatch(const nlohmann::json &input, 925 const EthernetInterfaceData ð_data, 926 const std::shared_ptr<AsyncResp> &asyncResp) { 927 if (input.is_string()) { 928 std::string newHostname = input.get<std::string>(); 929 930 if (eth_data.hostname == nullptr || newHostname != *eth_data.hostname) { 931 // Change hostname 932 ethernet_provider.setHostName( 933 newHostname, 934 [asyncResp, newHostname](const boost::system::error_code ec) { 935 if (ec) { 936 messages::addMessageToJson(asyncResp->res.json_value, 937 messages::internalError(), 938 "/HostName"); 939 } else { 940 asyncResp->res.json_value["HostName"] = newHostname; 941 } 942 }); 943 } 944 } else { 945 messages::addMessageToJson( 946 asyncResp->res.json_value, 947 messages::propertyValueTypeError(input.dump(), "HostName"), 948 "/HostName"); 949 } 950 } 951 952 void handleIPv4Patch(const std::string &ifaceId, const nlohmann::json &input, 953 const std::vector<IPv4AddressData> &ipv4_data, 954 const std::shared_ptr<AsyncResp> &asyncResp) { 955 if (!input.is_array()) { 956 messages::addMessageToJson( 957 asyncResp->res.json_value, 958 messages::propertyValueTypeError(input.dump(), "IPv4Addresses"), 959 "/IPv4Addresses"); 960 return; 961 } 962 963 // According to Redfish PATCH definition, size must be at least equal 964 if (input.size() < ipv4_data.size()) { 965 // TODO(kkowalsk) This should be a message indicating that not enough 966 // data has been provided 967 messages::addMessageToJson(asyncResp->res.json_value, 968 messages::internalError(), "/IPv4Addresses"); 969 return; 970 } 971 972 json_util::Result addressFieldState; 973 json_util::Result subnetMaskFieldState; 974 json_util::Result addressOriginFieldState; 975 json_util::Result gatewayFieldState; 976 const std::string *addressFieldValue; 977 const std::string *subnetMaskFieldValue; 978 const std::string *addressOriginFieldValue = nullptr; 979 const std::string *gatewayFieldValue; 980 uint8_t subnetMaskAsPrefixLength; 981 std::string addressOriginInDBusFormat; 982 983 bool errorDetected = false; 984 for (unsigned int entryIdx = 0; entryIdx < input.size(); entryIdx++) { 985 // Check that entry is not of some unexpected type 986 if (!input[entryIdx].is_object() && !input[entryIdx].is_null()) { 987 // Invalid object type 988 messages::addMessageToJson( 989 asyncResp->res.json_value, 990 messages::propertyValueTypeError(input[entryIdx].dump(), 991 "IPv4Address"), 992 "/IPv4Addresses/" + std::to_string(entryIdx)); 993 994 continue; 995 } 996 997 // Try to load fields 998 addressFieldState = json_util::getString( 999 "Address", input[entryIdx], addressFieldValue, 1000 static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR), 1001 asyncResp->res.json_value, 1002 "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address"); 1003 subnetMaskFieldState = json_util::getString( 1004 "SubnetMask", input[entryIdx], subnetMaskFieldValue, 1005 static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR), 1006 asyncResp->res.json_value, 1007 "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask"); 1008 addressOriginFieldState = json_util::getString( 1009 "AddressOrigin", input[entryIdx], addressOriginFieldValue, 1010 static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR), 1011 asyncResp->res.json_value, 1012 "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin"); 1013 gatewayFieldState = json_util::getString( 1014 "Gateway", input[entryIdx], gatewayFieldValue, 1015 static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR), 1016 asyncResp->res.json_value, 1017 "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway"); 1018 1019 if (addressFieldState == json_util::Result::WRONG_TYPE || 1020 subnetMaskFieldState == json_util::Result::WRONG_TYPE || 1021 addressOriginFieldState == json_util::Result::WRONG_TYPE || 1022 gatewayFieldState == json_util::Result::WRONG_TYPE) { 1023 return; 1024 } 1025 1026 if (addressFieldState == json_util::Result::SUCCESS && 1027 !ethernet_provider.ipv4VerifyIpAndGetBitcount(*addressFieldValue)) { 1028 errorDetected = true; 1029 messages::addMessageToJson( 1030 asyncResp->res.json_value, 1031 messages::propertyValueFormatError(*addressFieldValue, "Address"), 1032 "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address"); 1033 } 1034 1035 if (subnetMaskFieldState == json_util::Result::SUCCESS && 1036 !ethernet_provider.ipv4VerifyIpAndGetBitcount( 1037 *subnetMaskFieldValue, &subnetMaskAsPrefixLength)) { 1038 errorDetected = true; 1039 messages::addMessageToJson( 1040 asyncResp->res.json_value, 1041 messages::propertyValueFormatError(*subnetMaskFieldValue, 1042 "SubnetMask"), 1043 "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask"); 1044 } 1045 1046 // Get Address origin in proper format 1047 addressOriginInDBusFormat = 1048 ethernet_provider.translateAddressOriginBetweenDBusAndRedfish( 1049 addressOriginFieldValue, true, false); 1050 1051 if (addressOriginFieldState == json_util::Result::SUCCESS && 1052 addressOriginInDBusFormat.empty()) { 1053 errorDetected = true; 1054 messages::addMessageToJson( 1055 asyncResp->res.json_value, 1056 messages::propertyValueNotInList(*addressOriginFieldValue, 1057 "AddressOrigin"), 1058 "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin"); 1059 } 1060 1061 if (gatewayFieldState == json_util::Result::SUCCESS && 1062 !ethernet_provider.ipv4VerifyIpAndGetBitcount(*gatewayFieldValue)) { 1063 errorDetected = true; 1064 messages::addMessageToJson( 1065 asyncResp->res.json_value, 1066 messages::propertyValueFormatError(*gatewayFieldValue, "Gateway"), 1067 "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway"); 1068 } 1069 1070 // If any error occured do not proceed with current entry, but do not 1071 // end loop 1072 if (errorDetected) { 1073 errorDetected = false; 1074 continue; 1075 } 1076 1077 if (entryIdx >= ipv4_data.size()) { 1078 asyncResp->res.json_value["IPv4Addresses"][entryIdx] = input[entryIdx]; 1079 1080 // Verify that all field were provided 1081 if (addressFieldState == json_util::Result::NOT_EXIST) { 1082 errorDetected = true; 1083 messages::addMessageToJson( 1084 asyncResp->res.json_value, messages::propertyMissing("Address"), 1085 "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address"); 1086 } 1087 1088 if (subnetMaskFieldState == json_util::Result::NOT_EXIST) { 1089 errorDetected = true; 1090 messages::addMessageToJson( 1091 asyncResp->res.json_value, 1092 messages::propertyMissing("SubnetMask"), 1093 "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask"); 1094 } 1095 1096 if (addressOriginFieldState == json_util::Result::NOT_EXIST) { 1097 errorDetected = true; 1098 messages::addMessageToJson( 1099 asyncResp->res.json_value, 1100 messages::propertyMissing("AddressOrigin"), 1101 "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin"); 1102 } 1103 1104 if (gatewayFieldState == json_util::Result::NOT_EXIST) { 1105 errorDetected = true; 1106 messages::addMessageToJson( 1107 asyncResp->res.json_value, messages::propertyMissing("Gateway"), 1108 "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway"); 1109 } 1110 1111 // If any error occured do not proceed with current entry, but do not 1112 // end loop 1113 if (errorDetected) { 1114 errorDetected = false; 1115 continue; 1116 } 1117 1118 // Create IPv4 with provided data 1119 ethernet_provider.createIPv4( 1120 ifaceId, entryIdx, subnetMaskAsPrefixLength, *gatewayFieldValue, 1121 *addressFieldValue, asyncResp); 1122 } else { 1123 // Existing object that should be modified/deleted/remain unchanged 1124 if (input[entryIdx].is_null()) { 1125 // Object should be deleted 1126 ethernet_provider.deleteIPv4(ifaceId, ipv4_data[entryIdx].id, 1127 entryIdx, asyncResp); 1128 } else if (input[entryIdx].is_object()) { 1129 if (input[entryIdx].size() == 0) { 1130 // Object shall remain unchanged 1131 continue; 1132 } 1133 1134 // Apply changes 1135 if (addressFieldState == json_util::Result::SUCCESS && 1136 ipv4_data[entryIdx].address != nullptr && 1137 *ipv4_data[entryIdx].address != *addressFieldValue) { 1138 ethernet_provider.changeIPv4AddressProperty( 1139 ifaceId, entryIdx, ipv4_data[entryIdx].id, "Address", 1140 *addressFieldValue, asyncResp); 1141 } 1142 1143 if (subnetMaskFieldState == json_util::Result::SUCCESS && 1144 ipv4_data[entryIdx].netmask != *subnetMaskFieldValue) { 1145 ethernet_provider.changeIPv4SubnetMaskProperty( 1146 ifaceId, entryIdx, ipv4_data[entryIdx].id, 1147 *subnetMaskFieldValue, subnetMaskAsPrefixLength, asyncResp); 1148 } 1149 1150 if (addressOriginFieldState == json_util::Result::SUCCESS && 1151 ipv4_data[entryIdx].origin != *addressFieldValue) { 1152 ethernet_provider.changeIPv4Origin( 1153 ifaceId, entryIdx, ipv4_data[entryIdx].id, 1154 *addressOriginFieldValue, addressOriginInDBusFormat, asyncResp); 1155 } 1156 1157 if (gatewayFieldState == json_util::Result::SUCCESS && 1158 ipv4_data[entryIdx].gateway != nullptr && 1159 *ipv4_data[entryIdx].gateway != *gatewayFieldValue) { 1160 ethernet_provider.changeIPv4AddressProperty( 1161 ifaceId, entryIdx, ipv4_data[entryIdx].id, "Gateway", 1162 *gatewayFieldValue, asyncResp); 1163 } 1164 } 1165 } 1166 } 1167 } 1168 1169 nlohmann::json parseInterfaceData( 1170 const std::string &iface_id, const EthernetInterfaceData ð_data, 1171 const std::vector<IPv4AddressData> &ipv4_data) { 1172 // Copy JSON object to avoid race condition 1173 nlohmann::json json_response(Node::json); 1174 1175 // Fill out obvious data... 1176 json_response["Id"] = iface_id; 1177 json_response["@odata.id"] = 1178 "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id; 1179 1180 // ... then the one from DBus, regarding eth iface... 1181 if (eth_data.speed != nullptr) json_response["SpeedMbps"] = *eth_data.speed; 1182 1183 if (eth_data.mac_address != nullptr) 1184 json_response["MACAddress"] = *eth_data.mac_address; 1185 1186 if (eth_data.hostname != nullptr) 1187 json_response["HostName"] = *eth_data.hostname; 1188 1189 if (eth_data.vlan_id != nullptr) { 1190 nlohmann::json &vlanObj = json_response["VLAN"]; 1191 vlanObj["VLANEnable"] = true; 1192 vlanObj["VLANId"] = *eth_data.vlan_id; 1193 } 1194 1195 // ... at last, check if there are IPv4 data and prepare appropriate 1196 // collection 1197 if (ipv4_data.size() > 0) { 1198 nlohmann::json ipv4_array = nlohmann::json::array(); 1199 for (auto &ipv4_config : ipv4_data) { 1200 nlohmann::json json_ipv4; 1201 if (ipv4_config.address != nullptr) { 1202 json_ipv4["Address"] = *ipv4_config.address; 1203 if (ipv4_config.gateway != nullptr) 1204 json_ipv4["Gateway"] = *ipv4_config.gateway; 1205 1206 json_ipv4["AddressOrigin"] = ipv4_config.origin; 1207 json_ipv4["SubnetMask"] = ipv4_config.netmask; 1208 1209 ipv4_array.push_back(std::move(json_ipv4)); 1210 } 1211 } 1212 json_response["IPv4Addresses"] = std::move(ipv4_array); 1213 } 1214 1215 return json_response; 1216 } 1217 1218 /** 1219 * Functions triggers appropriate requests on DBus 1220 */ 1221 void doGet(crow::response &res, const crow::request &req, 1222 const std::vector<std::string> ¶ms) override { 1223 // TODO(Pawel) this shall be parametrized call (two params) to get 1224 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 1225 // Check if there is required param, truly entering this shall be 1226 // impossible. 1227 if (params.size() != 1) { 1228 res.result(boost::beast::http::status::internal_server_error); 1229 res.end(); 1230 return; 1231 } 1232 1233 const std::string &iface_id = params[0]; 1234 1235 // Get single eth interface data, and call the below callback for JSON 1236 // preparation 1237 ethernet_provider.getEthernetIfaceData( 1238 iface_id, [&, iface_id](const bool &success, 1239 const EthernetInterfaceData ð_data, 1240 const std::vector<IPv4AddressData> &ipv4_data) { 1241 if (success) { 1242 res.json_value = parseInterfaceData(iface_id, eth_data, ipv4_data); 1243 } else { 1244 // ... otherwise return error 1245 // TODO(Pawel)consider distinguish between non existing object, and 1246 // other errors 1247 messages::addMessageToErrorJson( 1248 res.json_value, 1249 messages::resourceNotFound("EthernetInterface", iface_id)); 1250 res.result(boost::beast::http::status::not_found); 1251 } 1252 res.end(); 1253 }); 1254 } 1255 1256 void doPatch(crow::response &res, const crow::request &req, 1257 const std::vector<std::string> ¶ms) override { 1258 // TODO(Pawel) this shall be parametrized call (two params) to get 1259 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 1260 // Check if there is required param, truly entering this shall be 1261 // impossible. 1262 if (params.size() != 1) { 1263 res.result(boost::beast::http::status::internal_server_error); 1264 res.end(); 1265 return; 1266 } 1267 1268 const std::string &iface_id = params[0]; 1269 1270 nlohmann::json patchReq; 1271 1272 if (!json_util::processJsonFromRequest(res, req, patchReq)) { 1273 return; 1274 } 1275 1276 // Get single eth interface data, and call the below callback for JSON 1277 // preparation 1278 ethernet_provider.getEthernetIfaceData( 1279 iface_id, 1280 [&, iface_id, patchReq = std::move(patchReq) ]( 1281 const bool &success, const EthernetInterfaceData ð_data, 1282 const std::vector<IPv4AddressData> &ipv4_data) { 1283 if (!success) { 1284 // ... otherwise return error 1285 // TODO(Pawel)consider distinguish between non existing object, and 1286 // other errors 1287 res.result(boost::beast::http::status::not_found); 1288 res.end(); 1289 1290 return; 1291 } 1292 1293 res.json_value = parseInterfaceData(iface_id, eth_data, ipv4_data); 1294 1295 std::shared_ptr<AsyncResp> asyncResp = 1296 std::make_shared<AsyncResp>(res); 1297 1298 for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end(); 1299 ++propertyIt) { 1300 if (propertyIt.key() == "VLAN") { 1301 handleVlanPatch(iface_id, propertyIt.value(), eth_data, "/VLAN", 1302 asyncResp); 1303 } else if (propertyIt.key() == "HostName") { 1304 handleHostnamePatch(propertyIt.value(), eth_data, asyncResp); 1305 } else if (propertyIt.key() == "IPv4Addresses") { 1306 handleIPv4Patch(iface_id, propertyIt.value(), ipv4_data, 1307 asyncResp); 1308 } else if (propertyIt.key() == "IPv6Addresses") { 1309 // TODO(kkowalsk) IPv6 Not supported on D-Bus yet 1310 messages::addMessageToJsonRoot( 1311 res.json_value, 1312 messages::propertyNotWritable(propertyIt.key())); 1313 } else { 1314 auto fieldInJsonIt = res.json_value.find(propertyIt.key()); 1315 1316 if (fieldInJsonIt == res.json_value.end()) { 1317 // Field not in scope of defined fields 1318 messages::addMessageToJsonRoot( 1319 res.json_value, 1320 messages::propertyUnknown(propertyIt.key())); 1321 } else if (*fieldInJsonIt != *propertyIt) { 1322 // User attempted to modify non-writable field 1323 messages::addMessageToJsonRoot( 1324 res.json_value, 1325 messages::propertyNotWritable(propertyIt.key())); 1326 } 1327 } 1328 } 1329 }); 1330 } 1331 1332 // Ethernet Provider object 1333 // TODO(Pawel) consider move it to singleton 1334 OnDemandEthernetProvider ethernet_provider; 1335 }; 1336 1337 class VlanNetworkInterfaceCollection; 1338 1339 /** 1340 * VlanNetworkInterface derived class for delivering VLANNetworkInterface Schema 1341 */ 1342 class VlanNetworkInterface : public Node { 1343 public: 1344 /* 1345 * Default Constructor 1346 */ 1347 template <typename CrowApp> 1348 // TODO(Pawel) Remove line from below, where we assume that there is only one 1349 // manager called openbmc This shall be generic, but requires to update 1350 // GetSubroutes method 1351 VlanNetworkInterface(CrowApp &app) 1352 : Node(app, 1353 "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/" 1354 "<str>", 1355 std::string(), std::string()) { 1356 Node::json["@odata.type"] = 1357 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 1358 Node::json["@odata.context"] = 1359 "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface"; 1360 Node::json["Name"] = "VLAN Network Interface"; 1361 1362 entityPrivileges = { 1363 {boost::beast::http::verb::get, {{"Login"}}}, 1364 {boost::beast::http::verb::head, {{"Login"}}}, 1365 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1366 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1367 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1368 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1369 } 1370 1371 private: 1372 nlohmann::json parseInterfaceData( 1373 const std::string &parent_iface_id, const std::string &iface_id, 1374 const EthernetInterfaceData ð_data, 1375 const std::vector<IPv4AddressData> &ipv4_data) { 1376 // Copy JSON object to avoid race condition 1377 nlohmann::json json_response(Node::json); 1378 1379 // Fill out obvious data... 1380 json_response["Id"] = iface_id; 1381 json_response["@odata.id"] = 1382 "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + parent_iface_id + 1383 "/VLANs/" + iface_id; 1384 1385 json_response["VLANEnable"] = true; 1386 json_response["VLANId"] = *eth_data.vlan_id; 1387 1388 return json_response; 1389 } 1390 1391 bool verifyNames(crow::response &res, const std::string &parent, 1392 const std::string &iface) { 1393 if (!boost::starts_with(iface, parent + "_")) { 1394 messages::addMessageToErrorJson( 1395 res.json_value, 1396 messages::resourceNotFound("VLAN Network Interface", iface)); 1397 res.result(boost::beast::http::status::bad_request); 1398 res.end(); 1399 1400 return false; 1401 } else { 1402 return true; 1403 } 1404 } 1405 1406 /** 1407 * Functions triggers appropriate requests on DBus 1408 */ 1409 void doGet(crow::response &res, const crow::request &req, 1410 const std::vector<std::string> ¶ms) override { 1411 // TODO(Pawel) this shall be parametrized call (two params) to get 1412 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 1413 // Check if there is required param, truly entering this shall be 1414 // impossible. 1415 if (params.size() != 2) { 1416 res.result(boost::beast::http::status::internal_server_error); 1417 res.end(); 1418 return; 1419 } 1420 1421 const std::string &parent_iface_id = params[0]; 1422 const std::string &iface_id = params[1]; 1423 1424 if (!verifyNames(res, parent_iface_id, iface_id)) { 1425 return; 1426 } 1427 1428 // Get single eth interface data, and call the below callback for JSON 1429 // preparation 1430 ethernet_provider.getEthernetIfaceData( 1431 iface_id, 1432 [&, parent_iface_id, iface_id]( 1433 const bool &success, const EthernetInterfaceData ð_data, 1434 const std::vector<IPv4AddressData> &ipv4_data) { 1435 if (success && eth_data.vlan_id != nullptr) { 1436 res.json_value = parseInterfaceData(parent_iface_id, iface_id, 1437 eth_data, ipv4_data); 1438 } else { 1439 // ... otherwise return error 1440 // TODO(Pawel)consider distinguish between non existing object, 1441 // and 1442 // other errors 1443 res.result(boost::beast::http::status::not_found); 1444 } 1445 res.end(); 1446 }); 1447 } 1448 1449 void doPatch(crow::response &res, const crow::request &req, 1450 const std::vector<std::string> ¶ms) override { 1451 if (params.size() != 2) { 1452 res.result(boost::beast::http::status::internal_server_error); 1453 res.end(); 1454 return; 1455 } 1456 1457 const std::string &parent_iface_id = params[0]; 1458 const std::string &iface_id = params[1]; 1459 1460 if (!verifyNames(res, parent_iface_id, iface_id)) { 1461 return; 1462 } 1463 1464 nlohmann::json patchReq; 1465 1466 if (!json_util::processJsonFromRequest(res, req, patchReq)) { 1467 return; 1468 } 1469 1470 // Get single eth interface data, and call the below callback for JSON 1471 // preparation 1472 ethernet_provider.getEthernetIfaceData( 1473 iface_id, 1474 [&, parent_iface_id, iface_id, patchReq = std::move(patchReq) ]( 1475 const bool &success, const EthernetInterfaceData ð_data, 1476 const std::vector<IPv4AddressData> &ipv4_data) { 1477 if (!success) { 1478 // ... otherwise return error 1479 // TODO(Pawel)consider distinguish between non existing object, 1480 // and 1481 // other errors 1482 res.result(boost::beast::http::status::not_found); 1483 res.end(); 1484 1485 return; 1486 } 1487 1488 res.json_value = parseInterfaceData(parent_iface_id, iface_id, 1489 eth_data, ipv4_data); 1490 1491 std::shared_ptr<AsyncResp> asyncResp = 1492 std::make_shared<AsyncResp>(res); 1493 1494 for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end(); 1495 ++propertyIt) { 1496 if (propertyIt.key() != "VLANEnable" && 1497 propertyIt.key() != "VLANId") { 1498 auto fieldInJsonIt = res.json_value.find(propertyIt.key()); 1499 1500 if (fieldInJsonIt == res.json_value.end()) { 1501 // Field not in scope of defined fields 1502 messages::addMessageToJsonRoot( 1503 res.json_value, 1504 messages::propertyUnknown(propertyIt.key())); 1505 } else if (*fieldInJsonIt != *propertyIt) { 1506 // User attempted to modify non-writable field 1507 messages::addMessageToJsonRoot( 1508 res.json_value, 1509 messages::propertyNotWritable(propertyIt.key())); 1510 } 1511 } 1512 } 1513 1514 EthernetInterface::handleVlanPatch(iface_id, patchReq, eth_data, "/", 1515 asyncResp); 1516 }); 1517 } 1518 1519 void doDelete(crow::response &res, const crow::request &req, 1520 const std::vector<std::string> ¶ms) override { 1521 if (params.size() != 2) { 1522 res.result(boost::beast::http::status::internal_server_error); 1523 res.end(); 1524 return; 1525 } 1526 1527 const std::string &parent_iface_id = params[0]; 1528 const std::string &iface_id = params[1]; 1529 1530 if (!verifyNames(res, parent_iface_id, iface_id)) { 1531 return; 1532 } 1533 1534 // Get single eth interface data, and call the below callback for JSON 1535 // preparation 1536 ethernet_provider.getEthernetIfaceData( 1537 iface_id, 1538 [&, parent_iface_id, iface_id]( 1539 const bool &success, const EthernetInterfaceData ð_data, 1540 const std::vector<IPv4AddressData> &ipv4_data) { 1541 if (success && eth_data.vlan_id != nullptr) { 1542 res.json_value = parseInterfaceData(parent_iface_id, iface_id, 1543 eth_data, ipv4_data); 1544 1545 // Disable VLAN 1546 OnDemandEthernetProvider::disableVlan( 1547 iface_id, [&](const boost::system::error_code ec) { 1548 if (ec) { 1549 res.json_value = nlohmann::json::object(); 1550 messages::addMessageToErrorJson(res.json_value, 1551 messages::internalError()); 1552 res.result( 1553 boost::beast::http::status::internal_server_error); 1554 } 1555 res.end(); 1556 }); 1557 } else { 1558 // ... otherwise return error 1559 // TODO(Pawel)consider distinguish between non existing object, 1560 // and 1561 // other errors 1562 1563 res.result(boost::beast::http::status::not_found); 1564 res.end(); 1565 } 1566 }); 1567 } 1568 1569 /** 1570 * This allows VlanNetworkInterfaceCollection to reuse this class' doGet 1571 * method, to maintain consistency of returned data, as Collection's doPost 1572 * should return data for created member which should match member's doGet 1573 * result in 100%. 1574 */ 1575 friend VlanNetworkInterfaceCollection; 1576 1577 // Ethernet Provider object 1578 // TODO(Pawel) consider move it to singleton 1579 OnDemandEthernetProvider ethernet_provider; 1580 }; 1581 1582 /** 1583 * VlanNetworkInterfaceCollection derived class for delivering 1584 * VLANNetworkInterface Collection Schema 1585 */ 1586 class VlanNetworkInterfaceCollection : public Node { 1587 public: 1588 template <typename CrowApp> 1589 // TODO(Pawel) Remove line from below, where we assume that there is only one 1590 // manager called openbmc This shall be generic, but requires to update 1591 // GetSubroutes method 1592 VlanNetworkInterfaceCollection(CrowApp &app) 1593 : Node(app, 1594 "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/", 1595 std::string()), 1596 memberVlan(app) { 1597 Node::json["@odata.type"] = 1598 "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection"; 1599 Node::json["@odata.context"] = 1600 "/redfish/v1/$metadata" 1601 "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection"; 1602 Node::json["Name"] = "VLAN Network Interface Collection"; 1603 1604 entityPrivileges = { 1605 {boost::beast::http::verb::get, {{"Login"}}}, 1606 {boost::beast::http::verb::head, {{"Login"}}}, 1607 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1608 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1609 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1610 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1611 } 1612 1613 private: 1614 /** 1615 * Functions triggers appropriate requests on DBus 1616 */ 1617 void doGet(crow::response &res, const crow::request &req, 1618 const std::vector<std::string> ¶ms) override { 1619 if (params.size() != 1) { 1620 // This means there is a problem with the router 1621 res.result(boost::beast::http::status::internal_server_error); 1622 res.end(); 1623 1624 return; 1625 } 1626 1627 // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for 1628 // any Manager, not only hardcoded 'openbmc'. 1629 std::string manager_id = "openbmc"; 1630 std::string rootInterfaceName = params[0]; 1631 1632 // Get eth interface list, and call the below callback for JSON preparation 1633 ethernet_provider.getEthernetIfaceList([ 1634 &, manager_id{std::move(manager_id)}, 1635 rootInterfaceName{std::move(rootInterfaceName)} 1636 ](const bool &success, const std::vector<std::string> &iface_list) { 1637 if (success) { 1638 bool rootInterfaceFound = false; 1639 nlohmann::json iface_array = nlohmann::json::array(); 1640 1641 for (const std::string &iface_item : iface_list) { 1642 if (iface_item == rootInterfaceName) { 1643 rootInterfaceFound = true; 1644 } else if (boost::starts_with(iface_item, rootInterfaceName + "_")) { 1645 iface_array.push_back( 1646 {{"@odata.id", "/redfish/v1/Managers/" + manager_id + 1647 "/EthernetInterfaces/" + rootInterfaceName + 1648 "/VLANs/" + iface_item}}); 1649 } 1650 } 1651 1652 if (rootInterfaceFound) { 1653 Node::json["Members"] = iface_array; 1654 Node::json["Members@odata.count"] = iface_array.size(); 1655 Node::json["@odata.id"] = "/redfish/v1/Managers/" + manager_id + 1656 "/EthernetInterfaces/" + rootInterfaceName + 1657 "/VLANs"; 1658 res.json_value = Node::json; 1659 } else { 1660 messages::addMessageToErrorJson( 1661 res.json_value, messages::resourceNotFound("EthernetInterface", 1662 rootInterfaceName)); 1663 res.result(boost::beast::http::status::not_found); 1664 res.end(); 1665 } 1666 } else { 1667 // No success, best what we can do is return INTERNALL ERROR 1668 res.result(boost::beast::http::status::internal_server_error); 1669 } 1670 res.end(); 1671 }); 1672 } 1673 1674 void doPost(crow::response &res, const crow::request &req, 1675 const std::vector<std::string> ¶ms) override { 1676 if (params.size() != 1) { 1677 // This means there is a problem with the router 1678 res.result(boost::beast::http::status::internal_server_error); 1679 res.end(); 1680 return; 1681 } 1682 1683 nlohmann::json postReq; 1684 1685 if (!json_util::processJsonFromRequest(res, req, postReq)) { 1686 return; 1687 } 1688 1689 // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for 1690 // any Manager, not only hardcoded 'openbmc'. 1691 std::string manager_id = "openbmc"; 1692 std::string rootInterfaceName = params[0]; 1693 uint64_t vlanId; 1694 bool errorDetected; 1695 1696 if (json_util::getUnsigned( 1697 "VLANId", postReq, vlanId, 1698 static_cast<uint8_t>(json_util::MessageSetting::MISSING) | 1699 static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR), 1700 res.json_value, "/VLANId") != json_util::Result::SUCCESS) { 1701 res.end(); 1702 return; 1703 } 1704 1705 // Get eth interface list, and call the below callback for JSON preparation 1706 ethernet_provider.getEthernetIfaceList([ 1707 &, manager_id{std::move(manager_id)}, 1708 rootInterfaceName{std::move(rootInterfaceName)} 1709 ](const bool &success, const std::vector<std::string> &iface_list) { 1710 if (success) { 1711 bool rootInterfaceFound = false; 1712 1713 for (const std::string &iface_item : iface_list) { 1714 if (iface_item == rootInterfaceName) { 1715 rootInterfaceFound = true; 1716 break; 1717 } 1718 } 1719 1720 if (rootInterfaceFound) { 1721 ethernet_provider.createVlan( 1722 rootInterfaceName, vlanId, 1723 [&, vlanId, rootInterfaceName, 1724 req{std::move(req)} ](const boost::system::error_code ec) { 1725 if (ec) { 1726 messages::addMessageToErrorJson(res.json_value, 1727 messages::internalError()); 1728 res.end(); 1729 } else { 1730 memberVlan.doGet( 1731 res, req, 1732 {rootInterfaceName, 1733 rootInterfaceName + "_" + std::to_string(vlanId)}); 1734 } 1735 }); 1736 } else { 1737 messages::addMessageToErrorJson( 1738 res.json_value, messages::resourceNotFound("EthernetInterface", 1739 rootInterfaceName)); 1740 res.result(boost::beast::http::status::not_found); 1741 res.end(); 1742 } 1743 } else { 1744 // No success, best what we can do is return INTERNALL ERROR 1745 res.result(boost::beast::http::status::internal_server_error); 1746 res.end(); 1747 } 1748 }); 1749 } 1750 1751 // Ethernet Provider object 1752 // TODO(Pawel) consider move it to singleton 1753 OnDemandEthernetProvider ethernet_provider; 1754 VlanNetworkInterface memberVlan; 1755 }; 1756 1757 } // namespace redfish 1758