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 <dbus_singleton.hpp> 21 #include <error_messages.hpp> 22 #include <node.hpp> 23 #include <optional> 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 std::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, bool vlanEnable, 854 uint64_t vlanId, 855 const EthernetInterfaceData ðData, 856 const std::shared_ptr<AsyncResp> asyncResp) 857 { 858 if (!ethData.vlan_id) 859 { 860 // This interface is not a VLAN. Cannot do anything with it 861 // TODO(kkowalsk) Change this message 862 messages::propertyNotWritable(asyncResp->res, "VLANEnable"); 863 864 return; 865 } 866 867 // VLAN is configured on the interface 868 if (vlanEnable == true) 869 { 870 // Change VLAN Id 871 asyncResp->res.jsonValue["VLANId"] = vlanId; 872 auto callback = [asyncResp](const boost::system::error_code ec) { 873 if (ec) 874 { 875 messages::internalError(asyncResp->res); 876 } 877 else 878 { 879 asyncResp->res.jsonValue["VLANEnable"] = true; 880 } 881 }; 882 crow::connections::systemBus->async_method_call( 883 std::move(callback), "xyz.openbmc_project.Network", 884 "/xyz/openbmc_project/network/" + ifaceId, 885 "org.freedesktop.DBus.Properties", "Set", 886 "xyz.openbmc_project.Network.VLAN", "Id", 887 sdbusplus::message::variant<uint32_t>(vlanId)); 888 } 889 else 890 { 891 auto callback = [asyncResp](const boost::system::error_code ec) { 892 if (ec) 893 { 894 messages::internalError(asyncResp->res); 895 return; 896 } 897 asyncResp->res.jsonValue["VLANEnable"] = false; 898 }; 899 900 crow::connections::systemBus->async_method_call( 901 std::move(callback), "xyz.openbmc_project.Network", 902 "/xyz/openbmc_project/network/" + ifaceId, 903 "xyz.openbmc_project.Object.Delete", "Delete"); 904 } 905 } 906 907 private: 908 void handleHostnamePatch(const nlohmann::json &input, 909 const std::shared_ptr<AsyncResp> asyncResp) 910 { 911 const std::string *newHostname = input.get_ptr<const std::string *>(); 912 if (newHostname == nullptr) 913 { 914 messages::propertyValueTypeError(asyncResp->res, input.dump(), 915 "HostName"); 916 return; 917 } 918 919 // Change hostname 920 setHostName(*newHostname, 921 [asyncResp, newHostname{std::string(*newHostname)}]( 922 const boost::system::error_code ec) { 923 if (ec) 924 { 925 messages::internalError(asyncResp->res); 926 } 927 else 928 { 929 asyncResp->res.jsonValue["HostName"] = newHostname; 930 } 931 }); 932 } 933 934 void handleIPv4Patch( 935 const std::string &ifaceId, const nlohmann::json &input, 936 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 937 const std::shared_ptr<AsyncResp> asyncResp) 938 { 939 if (!input.is_array()) 940 { 941 messages::propertyValueTypeError(asyncResp->res, input.dump(), 942 "IPv4Addresses"); 943 return; 944 } 945 946 // According to Redfish PATCH definition, size must be at least equal 947 if (input.size() < ipv4Data.size()) 948 { 949 messages::propertyValueFormatError(asyncResp->res, input.dump(), 950 "IPv4Addresses"); 951 return; 952 } 953 954 int entryIdx = 0; 955 boost::container::flat_set<IPv4AddressData>::const_iterator thisData = 956 ipv4Data.begin(); 957 for (const nlohmann::json &thisJson : input) 958 { 959 std::string pathString = 960 "IPv4Addresses/" + std::to_string(entryIdx); 961 // Check that entry is not of some unexpected type 962 if (!thisJson.is_object() && !thisJson.is_null()) 963 { 964 messages::propertyValueTypeError(asyncResp->res, 965 thisJson.dump(), 966 pathString + "/IPv4Address"); 967 968 continue; 969 } 970 971 nlohmann::json::const_iterator addressFieldIt = 972 thisJson.find("Address"); 973 const std::string *addressField = nullptr; 974 if (addressFieldIt != thisJson.end()) 975 { 976 addressField = addressFieldIt->get_ptr<const std::string *>(); 977 if (addressField == nullptr) 978 { 979 messages::propertyValueFormatError(asyncResp->res, 980 addressFieldIt->dump(), 981 pathString + "/Address"); 982 continue; 983 } 984 else 985 { 986 if (!ipv4VerifyIpAndGetBitcount(*addressField)) 987 { 988 messages::propertyValueFormatError( 989 asyncResp->res, *addressField, 990 pathString + "/Address"); 991 continue; 992 } 993 } 994 } 995 996 std::optional<uint8_t> prefixLength; 997 const std::string *subnetField = nullptr; 998 nlohmann::json::const_iterator subnetFieldIt = 999 thisJson.find("SubnetMask"); 1000 if (subnetFieldIt != thisJson.end()) 1001 { 1002 subnetField = subnetFieldIt->get_ptr<const std::string *>(); 1003 if (subnetField == nullptr) 1004 { 1005 messages::propertyValueFormatError( 1006 asyncResp->res, *subnetField, 1007 pathString + "/SubnetMask"); 1008 continue; 1009 } 1010 else 1011 { 1012 prefixLength = 0; 1013 if (!ipv4VerifyIpAndGetBitcount(*subnetField, 1014 &*prefixLength)) 1015 { 1016 messages::propertyValueFormatError( 1017 asyncResp->res, *subnetField, 1018 pathString + "/SubnetMask"); 1019 continue; 1020 } 1021 } 1022 } 1023 1024 std::string addressOriginInDBusFormat; 1025 const std::string *addressOriginField = nullptr; 1026 nlohmann::json::const_iterator addressOriginFieldIt = 1027 thisJson.find("AddressOrigin"); 1028 if (addressOriginFieldIt != thisJson.end()) 1029 { 1030 const std::string *addressOriginField = 1031 addressOriginFieldIt->get_ptr<const std::string *>(); 1032 if (addressOriginField == nullptr) 1033 { 1034 messages::propertyValueFormatError( 1035 asyncResp->res, *addressOriginField, 1036 pathString + "/AddressOrigin"); 1037 continue; 1038 } 1039 else 1040 { 1041 // Get Address origin in proper format 1042 addressOriginInDBusFormat = 1043 translateAddressOriginRedfishToDbus( 1044 *addressOriginField); 1045 if (addressOriginInDBusFormat.empty()) 1046 { 1047 messages::propertyValueNotInList( 1048 asyncResp->res, *addressOriginField, 1049 pathString + "/AddressOrigin"); 1050 continue; 1051 } 1052 } 1053 } 1054 1055 nlohmann::json::const_iterator gatewayFieldIt = 1056 thisJson.find("Gateway"); 1057 const std::string *gatewayField = nullptr; 1058 if (gatewayFieldIt != thisJson.end()) 1059 { 1060 const std::string *gatewayField = 1061 gatewayFieldIt->get_ptr<const std::string *>(); 1062 if (gatewayField == nullptr || 1063 !ipv4VerifyIpAndGetBitcount(*gatewayField)) 1064 { 1065 messages::propertyValueFormatError( 1066 asyncResp->res, *gatewayField, pathString + "/Gateway"); 1067 continue; 1068 } 1069 } 1070 1071 // if a vlan already exists, modify the existing 1072 if (thisData != ipv4Data.end()) 1073 { 1074 // Existing object that should be modified/deleted/remain 1075 // unchanged 1076 if (thisJson.is_null()) 1077 { 1078 auto callback = [entryIdx{std::to_string(entryIdx)}, 1079 asyncResp]( 1080 const boost::system::error_code ec) { 1081 if (ec) 1082 { 1083 messages::internalError(asyncResp->res); 1084 return; 1085 } 1086 asyncResp->res.jsonValue["IPv4Addresses"][entryIdx] = 1087 nullptr; 1088 }; 1089 crow::connections::systemBus->async_method_call( 1090 std::move(callback), "xyz.openbmc_project.Network", 1091 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + 1092 thisData->id, 1093 "xyz.openbmc_project.Object.Delete", "Delete"); 1094 } 1095 else if (thisJson.is_object()) 1096 { 1097 // Apply changes 1098 if (addressField != nullptr) 1099 { 1100 auto callback = 1101 [asyncResp, entryIdx, 1102 addressField{std::string(*addressField)}]( 1103 const boost::system::error_code ec) { 1104 if (ec) 1105 { 1106 messages::internalError(asyncResp->res); 1107 return; 1108 } 1109 asyncResp->res 1110 .jsonValue["IPv4Addresses"][std::to_string( 1111 entryIdx)]["Address"] = addressField; 1112 }; 1113 1114 crow::connections::systemBus->async_method_call( 1115 std::move(callback), "xyz.openbmc_project.Network", 1116 "/xyz/openbmc_project/network/" + ifaceId + 1117 "/ipv4/" + thisData->id, 1118 "org.freedesktop.DBus.Properties", "Set", 1119 "xyz.openbmc_project.Network.IP", "Address", 1120 sdbusplus::message::variant<std::string>( 1121 *addressField)); 1122 } 1123 1124 if (prefixLength && subnetField != nullptr) 1125 { 1126 changeIPv4SubnetMaskProperty(ifaceId, entryIdx, 1127 thisData->id, *subnetField, 1128 *prefixLength, asyncResp); 1129 } 1130 1131 if (!addressOriginInDBusFormat.empty() && 1132 addressOriginField != nullptr) 1133 { 1134 changeIPv4Origin(ifaceId, entryIdx, thisData->id, 1135 *addressOriginField, 1136 addressOriginInDBusFormat, asyncResp); 1137 } 1138 1139 if (gatewayField != nullptr) 1140 { 1141 auto callback = 1142 [asyncResp, entryIdx, 1143 gatewayField{std::string(*gatewayField)}]( 1144 const boost::system::error_code ec) { 1145 if (ec) 1146 { 1147 messages::internalError(asyncResp->res); 1148 return; 1149 } 1150 asyncResp->res 1151 .jsonValue["IPv4Addresses"][std::to_string( 1152 entryIdx)]["Gateway"] = 1153 std::move(gatewayField); 1154 }; 1155 1156 crow::connections::systemBus->async_method_call( 1157 std::move(callback), "xyz.openbmc_project.Network", 1158 "/xyz/openbmc_project/network/" + ifaceId + 1159 "/ipv4/" + thisData->id, 1160 "org.freedesktop.DBus.Properties", "Set", 1161 "xyz.openbmc_project.Network.IP", "Gateway", 1162 sdbusplus::message::variant<std::string>( 1163 *gatewayField)); 1164 } 1165 } 1166 thisData++; 1167 } 1168 else 1169 { 1170 // Create IPv4 with provided data 1171 if (gatewayField == nullptr) 1172 { 1173 messages::propertyMissing(asyncResp->res, 1174 pathString + "/Gateway"); 1175 continue; 1176 } 1177 1178 if (addressField == nullptr) 1179 { 1180 messages::propertyMissing(asyncResp->res, 1181 pathString + "/Address"); 1182 continue; 1183 } 1184 1185 if (!prefixLength) 1186 { 1187 messages::propertyMissing(asyncResp->res, 1188 pathString + "/SubnetMask"); 1189 continue; 1190 } 1191 1192 createIPv4(ifaceId, entryIdx, *prefixLength, *gatewayField, 1193 *addressField, asyncResp); 1194 asyncResp->res.jsonValue["IPv4Addresses"][entryIdx] = thisJson; 1195 } 1196 entryIdx++; 1197 } 1198 } 1199 1200 void parseInterfaceData( 1201 nlohmann::json &json_response, const std::string &iface_id, 1202 const EthernetInterfaceData ðData, 1203 const boost::container::flat_set<IPv4AddressData> &ipv4Data) 1204 { 1205 json_response["Id"] = iface_id; 1206 json_response["@odata.id"] = 1207 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + iface_id; 1208 1209 json_response["SpeedMbps"] = ethData.speed; 1210 json_response["MACAddress"] = ethData.mac_address; 1211 if (!ethData.hostname.empty()) 1212 { 1213 json_response["HostName"] = ethData.hostname; 1214 } 1215 1216 nlohmann::json &vlanObj = json_response["VLAN"]; 1217 if (ethData.vlan_id) 1218 { 1219 vlanObj["VLANEnable"] = true; 1220 vlanObj["VLANId"] = *ethData.vlan_id; 1221 } 1222 else 1223 { 1224 vlanObj["VLANEnable"] = false; 1225 vlanObj["VLANId"] = 0; 1226 } 1227 1228 if (ipv4Data.size() > 0) 1229 { 1230 nlohmann::json &ipv4_array = json_response["IPv4Addresses"]; 1231 ipv4_array = nlohmann::json::array(); 1232 for (auto &ipv4_config : ipv4Data) 1233 { 1234 if (!ipv4_config.address.empty()) 1235 { 1236 ipv4_array.push_back({{"AddressOrigin", ipv4_config.origin}, 1237 {"SubnetMask", ipv4_config.netmask}, 1238 {"Address", ipv4_config.address}}); 1239 1240 if (!ipv4_config.gateway.empty()) 1241 { 1242 ipv4_array.back()["Gateway"] = ipv4_config.gateway; 1243 } 1244 } 1245 } 1246 } 1247 } 1248 1249 /** 1250 * Functions triggers appropriate requests on DBus 1251 */ 1252 void doGet(crow::Response &res, const crow::Request &req, 1253 const std::vector<std::string> ¶ms) override 1254 { 1255 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1256 if (params.size() != 1) 1257 { 1258 messages::internalError(asyncResp->res); 1259 return; 1260 } 1261 1262 getEthernetIfaceData( 1263 params[0], 1264 [this, asyncResp, iface_id{std::string(params[0])}]( 1265 const bool &success, const EthernetInterfaceData ðData, 1266 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1267 if (!success) 1268 { 1269 // TODO(Pawel)consider distinguish between non existing 1270 // object, and other errors 1271 messages::resourceNotFound(asyncResp->res, 1272 "EthernetInterface", iface_id); 1273 return; 1274 } 1275 asyncResp->res.jsonValue["@odata.type"] = 1276 "#EthernetInterface.v1_2_0.EthernetInterface"; 1277 asyncResp->res.jsonValue["@odata.context"] = 1278 "/redfish/v1/$metadata#EthernetInterface.EthernetInterface"; 1279 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1280 asyncResp->res.jsonValue["Description"] = 1281 "Management Network Interface"; 1282 1283 parseInterfaceData(asyncResp->res.jsonValue, iface_id, ethData, 1284 ipv4Data); 1285 }); 1286 } 1287 1288 void doPatch(crow::Response &res, const crow::Request &req, 1289 const std::vector<std::string> ¶ms) override 1290 { 1291 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1292 if (params.size() != 1) 1293 { 1294 messages::internalError(asyncResp->res); 1295 return; 1296 } 1297 1298 const std::string &iface_id = params[0]; 1299 1300 std::optional<nlohmann::json> vlan; 1301 std::optional<nlohmann::json> hostname; 1302 std::optional<nlohmann::json> ipv4Addresses; 1303 std::optional<nlohmann::json> ipv6Addresses; 1304 1305 if (!json_util::readJson(req, res, "VLAN", vlan, "HostName", hostname, 1306 "IPv4Addresses", ipv4Addresses, 1307 "IPv6Addresses", ipv6Addresses)) 1308 { 1309 return; 1310 } 1311 std::optional<uint64_t> vlanId = 0; 1312 std::optional<bool> vlanEnable = false; 1313 if (vlan) 1314 { 1315 if (!json_util::readJson(*vlan, res, "VLANEnable", vlanEnable, 1316 "VLANId", vlanId)) 1317 { 1318 return; 1319 } 1320 // Need both vlanId and vlanEnable to service this request 1321 if (static_cast<bool>(vlanId) ^ static_cast<bool>(vlanEnable)) 1322 { 1323 if (vlanId) 1324 { 1325 messages::propertyMissing(asyncResp->res, "VLANEnable"); 1326 } 1327 else 1328 { 1329 messages::propertyMissing(asyncResp->res, "VLANId"); 1330 } 1331 1332 return; 1333 } 1334 } 1335 1336 // Get single eth interface data, and call the below callback for JSON 1337 // preparation 1338 getEthernetIfaceData( 1339 iface_id, 1340 [this, asyncResp, iface_id, vlanId, vlanEnable, 1341 hostname = std::move(hostname), 1342 ipv4Addresses = std::move(ipv4Addresses), 1343 ipv6Addresses = std::move(ipv6Addresses)]( 1344 const bool &success, const EthernetInterfaceData ðData, 1345 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1346 if (!success) 1347 { 1348 // ... otherwise return error 1349 // TODO(Pawel)consider distinguish between non existing 1350 // object, and other errors 1351 messages::resourceNotFound( 1352 asyncResp->res, "VLAN Network Interface", iface_id); 1353 return; 1354 } 1355 1356 parseInterfaceData(asyncResp->res.jsonValue, iface_id, ethData, 1357 ipv4Data); 1358 1359 if (vlanId && vlanEnable) 1360 { 1361 handleVlanPatch(iface_id, *vlanId, *vlanEnable, ethData, 1362 asyncResp); 1363 } 1364 1365 if (hostname) 1366 { 1367 handleHostnamePatch(*hostname, asyncResp); 1368 } 1369 1370 if (ipv4Addresses) 1371 { 1372 handleIPv4Patch(iface_id, *ipv4Addresses, ipv4Data, 1373 asyncResp); 1374 } 1375 1376 if (ipv6Addresses) 1377 { 1378 // TODO(kkowalsk) IPv6 Not supported on D-Bus yet 1379 messages::propertyNotWritable(asyncResp->res, 1380 "IPv6Addresses"); 1381 } 1382 }); 1383 } 1384 }; 1385 1386 /** 1387 * VlanNetworkInterface derived class for delivering VLANNetworkInterface 1388 * Schema 1389 */ 1390 class VlanNetworkInterface : public Node 1391 { 1392 public: 1393 /* 1394 * Default Constructor 1395 */ 1396 template <typename CrowApp> 1397 VlanNetworkInterface(CrowApp &app) : 1398 Node(app, 1399 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>", 1400 std::string(), std::string()) 1401 { 1402 entityPrivileges = { 1403 {boost::beast::http::verb::get, {{"Login"}}}, 1404 {boost::beast::http::verb::head, {{"Login"}}}, 1405 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1406 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1407 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1408 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1409 } 1410 1411 private: 1412 void parseInterfaceData( 1413 nlohmann::json &json_response, const std::string &parent_iface_id, 1414 const std::string &iface_id, const EthernetInterfaceData ðData, 1415 const boost::container::flat_set<IPv4AddressData> &ipv4Data) 1416 { 1417 // Fill out obvious data... 1418 json_response["Id"] = iface_id; 1419 json_response["@odata.id"] = 1420 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + parent_iface_id + 1421 "/VLANs/" + iface_id; 1422 1423 json_response["VLANEnable"] = true; 1424 if (ethData.vlan_id) 1425 { 1426 json_response["VLANId"] = *ethData.vlan_id; 1427 } 1428 } 1429 1430 bool verifyNames(crow::Response &res, const std::string &parent, 1431 const std::string &iface) 1432 { 1433 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1434 if (!boost::starts_with(iface, parent + "_")) 1435 { 1436 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 1437 iface); 1438 return false; 1439 } 1440 else 1441 { 1442 return true; 1443 } 1444 } 1445 1446 /** 1447 * Functions triggers appropriate requests on DBus 1448 */ 1449 void doGet(crow::Response &res, const crow::Request &req, 1450 const std::vector<std::string> ¶ms) override 1451 { 1452 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1453 // TODO(Pawel) this shall be parameterized call (two params) to get 1454 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 1455 // Check if there is required param, truly entering this shall be 1456 // impossible. 1457 if (params.size() != 2) 1458 { 1459 messages::internalError(res); 1460 res.end(); 1461 return; 1462 } 1463 1464 const std::string &parent_iface_id = params[0]; 1465 const std::string &iface_id = params[1]; 1466 res.jsonValue["@odata.type"] = 1467 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 1468 res.jsonValue["@odata.context"] = 1469 "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface"; 1470 res.jsonValue["Name"] = "VLAN Network Interface"; 1471 1472 if (!verifyNames(res, parent_iface_id, iface_id)) 1473 { 1474 return; 1475 } 1476 1477 // Get single eth interface data, and call the below callback for JSON 1478 // preparation 1479 getEthernetIfaceData( 1480 iface_id, 1481 [this, asyncResp, parent_iface_id, iface_id]( 1482 const bool &success, const EthernetInterfaceData ðData, 1483 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1484 if (success && ethData.vlan_id) 1485 { 1486 parseInterfaceData(asyncResp->res.jsonValue, 1487 parent_iface_id, iface_id, ethData, 1488 ipv4Data); 1489 } 1490 else 1491 { 1492 // ... otherwise return error 1493 // TODO(Pawel)consider distinguish between non existing 1494 // object, and other errors 1495 messages::resourceNotFound( 1496 asyncResp->res, "VLAN Network Interface", iface_id); 1497 } 1498 }); 1499 } 1500 1501 void doPatch(crow::Response &res, const crow::Request &req, 1502 const std::vector<std::string> ¶ms) override 1503 { 1504 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1505 if (params.size() != 2) 1506 { 1507 messages::internalError(asyncResp->res); 1508 return; 1509 } 1510 1511 const std::string &parentIfaceId = params[0]; 1512 const std::string &ifaceId = params[1]; 1513 1514 if (!verifyNames(res, parentIfaceId, ifaceId)) 1515 { 1516 return; 1517 } 1518 1519 bool vlanEnable = false; 1520 uint64_t vlanId = 0; 1521 1522 if (!json_util::readJson(req, res, "VLANEnable", vlanEnable, "VLANId", 1523 vlanId)) 1524 { 1525 return; 1526 } 1527 1528 // Get single eth interface data, and call the below callback for JSON 1529 // preparation 1530 getEthernetIfaceData( 1531 ifaceId, 1532 [this, asyncResp, parentIfaceId, ifaceId, vlanEnable, vlanId]( 1533 const bool &success, const EthernetInterfaceData ðData, 1534 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1535 if (!success) 1536 { 1537 // TODO(Pawel)consider distinguish between non existing 1538 // object, and other errors 1539 messages::resourceNotFound( 1540 asyncResp->res, "VLAN Network Interface", ifaceId); 1541 1542 return; 1543 } 1544 1545 parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId, 1546 ifaceId, ethData, ipv4Data); 1547 1548 EthernetInterface::handleVlanPatch(ifaceId, vlanId, vlanEnable, 1549 ethData, asyncResp); 1550 }); 1551 } 1552 1553 void doDelete(crow::Response &res, const crow::Request &req, 1554 const std::vector<std::string> ¶ms) override 1555 { 1556 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1557 if (params.size() != 2) 1558 { 1559 messages::internalError(asyncResp->res); 1560 return; 1561 } 1562 1563 const std::string &parentIfaceId = params[0]; 1564 const std::string &ifaceId = params[1]; 1565 1566 if (!verifyNames(asyncResp->res, parentIfaceId, ifaceId)) 1567 { 1568 return; 1569 } 1570 1571 // Get single eth interface data, and call the below callback for JSON 1572 // preparation 1573 getEthernetIfaceData( 1574 ifaceId, 1575 [this, asyncResp, parentIfaceId{std::string(parentIfaceId)}, 1576 ifaceId{std::string(ifaceId)}]( 1577 const bool &success, const EthernetInterfaceData ðData, 1578 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1579 if (success && ethData.vlan_id) 1580 { 1581 parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId, 1582 ifaceId, ethData, ipv4Data); 1583 1584 auto callback = 1585 [asyncResp](const boost::system::error_code ec) { 1586 if (ec) 1587 { 1588 messages::internalError(asyncResp->res); 1589 } 1590 }; 1591 crow::connections::systemBus->async_method_call( 1592 std::move(callback), "xyz.openbmc_project.Network", 1593 std::string("/xyz/openbmc_project/network/") + ifaceId, 1594 "xyz.openbmc_project.Object.Delete", "Delete"); 1595 } 1596 else 1597 { 1598 // ... otherwise return error 1599 // TODO(Pawel)consider distinguish between non existing 1600 // object, and other errors 1601 messages::resourceNotFound( 1602 asyncResp->res, "VLAN Network Interface", ifaceId); 1603 } 1604 }); 1605 } 1606 }; 1607 1608 /** 1609 * VlanNetworkInterfaceCollection derived class for delivering 1610 * VLANNetworkInterface Collection Schema 1611 */ 1612 class VlanNetworkInterfaceCollection : public Node 1613 { 1614 public: 1615 template <typename CrowApp> 1616 VlanNetworkInterfaceCollection(CrowApp &app) : 1617 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/", 1618 std::string()) 1619 { 1620 entityPrivileges = { 1621 {boost::beast::http::verb::get, {{"Login"}}}, 1622 {boost::beast::http::verb::head, {{"Login"}}}, 1623 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1624 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1625 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1626 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1627 } 1628 1629 private: 1630 /** 1631 * Functions triggers appropriate requests on DBus 1632 */ 1633 void doGet(crow::Response &res, const crow::Request &req, 1634 const std::vector<std::string> ¶ms) override 1635 { 1636 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1637 if (params.size() != 1) 1638 { 1639 // This means there is a problem with the router 1640 messages::internalError(asyncResp->res); 1641 return; 1642 } 1643 1644 const std::string &rootInterfaceName = params[0]; 1645 1646 // Get eth interface list, and call the below callback for JSON 1647 // preparation 1648 getEthernetIfaceList( 1649 [this, asyncResp, 1650 rootInterfaceName{std::string(rootInterfaceName)}]( 1651 const bool &success, 1652 const std::vector<std::string> &iface_list) { 1653 if (!success) 1654 { 1655 messages::internalError(asyncResp->res); 1656 return; 1657 } 1658 asyncResp->res.jsonValue["@odata.type"] = 1659 "#VLanNetworkInterfaceCollection." 1660 "VLanNetworkInterfaceCollection"; 1661 asyncResp->res.jsonValue["@odata.context"] = 1662 "/redfish/v1/$metadata" 1663 "#VLanNetworkInterfaceCollection." 1664 "VLanNetworkInterfaceCollection"; 1665 asyncResp->res.jsonValue["Name"] = 1666 "VLAN Network Interface Collection"; 1667 1668 nlohmann::json iface_array = nlohmann::json::array(); 1669 1670 for (const std::string &iface_item : iface_list) 1671 { 1672 if (boost::starts_with(iface_item, rootInterfaceName + "_")) 1673 { 1674 iface_array.push_back( 1675 {{"@odata.id", 1676 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1677 rootInterfaceName + "/VLANs/" + iface_item}}); 1678 } 1679 } 1680 1681 if (iface_array.empty()) 1682 { 1683 messages::resourceNotFound( 1684 asyncResp->res, "EthernetInterface", rootInterfaceName); 1685 return; 1686 } 1687 asyncResp->res.jsonValue["Members@odata.count"] = 1688 iface_array.size(); 1689 asyncResp->res.jsonValue["Members"] = std::move(iface_array); 1690 asyncResp->res.jsonValue["@odata.id"] = 1691 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1692 rootInterfaceName + "/VLANs"; 1693 }); 1694 } 1695 1696 void doPost(crow::Response &res, const crow::Request &req, 1697 const std::vector<std::string> ¶ms) override 1698 { 1699 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1700 if (params.size() != 1) 1701 { 1702 messages::internalError(asyncResp->res); 1703 return; 1704 } 1705 1706 uint32_t vlanId = 0; 1707 if (!json_util::readJson(req, res, "VLANId", vlanId)) 1708 { 1709 return; 1710 } 1711 const std::string &rootInterfaceName = params[0]; 1712 auto callback = [asyncResp](const boost::system::error_code ec) { 1713 if (ec) 1714 { 1715 // TODO(ed) make more consistent error messages based on 1716 // phosphor-network responses 1717 messages::internalError(asyncResp->res); 1718 return; 1719 } 1720 messages::created(asyncResp->res); 1721 }; 1722 crow::connections::systemBus->async_method_call( 1723 std::move(callback), "xyz.openbmc_project.Network", 1724 "/xyz/openbmc_project/network", 1725 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 1726 rootInterfaceName, vlanId); 1727 } 1728 }; 1729 } // namespace redfish 1730