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