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