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