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