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