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 #include <variant> 26 27 namespace redfish 28 { 29 30 /** 31 * DBus types primitives for several generic DBus interfaces 32 * TODO(Pawel) consider move this to separate file into boost::dbus 33 */ 34 using PropertiesMapType = boost::container::flat_map< 35 std::string, std::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 std::vector<std::string>>>>>>>; 47 48 enum class LinkType 49 { 50 Local, 51 Global 52 }; 53 54 /** 55 * Structure for keeping IPv4 data required by Redfish 56 */ 57 struct IPv4AddressData 58 { 59 std::string id; 60 std::string address; 61 std::string domain; 62 std::string gateway; 63 std::string netmask; 64 std::string origin; 65 LinkType linktype; 66 67 bool operator<(const IPv4AddressData &obj) const 68 { 69 return id < obj.id; 70 } 71 }; 72 73 /** 74 * Structure for keeping IPv6 data required by Redfish 75 */ 76 struct IPv6AddressData 77 { 78 std::string id; 79 std::string address; 80 std::string origin; 81 uint8_t prefixLength; 82 83 bool operator<(const IPv6AddressData &obj) const 84 { 85 return id < obj.id; 86 } 87 }; 88 /** 89 * Structure for keeping basic single Ethernet Interface information 90 * available from DBus 91 */ 92 struct EthernetInterfaceData 93 { 94 uint32_t speed; 95 bool auto_neg; 96 bool DNSEnabled; 97 bool NTPEnabled; 98 bool HostNameEnabled; 99 bool SendHostNameEnabled; 100 bool linkUp; 101 bool nicEnabled; 102 std::string DHCPEnabled; 103 std::string operatingMode; 104 std::string hostname; 105 std::string default_gateway; 106 std::string ipv6_default_gateway; 107 std::string mac_address; 108 std::vector<std::uint32_t> vlan_id; 109 std::vector<std::string> nameservers; 110 std::vector<std::string> domainnames; 111 }; 112 113 struct DHCPParameters 114 { 115 std::optional<bool> dhcpv4Enabled; 116 std::optional<bool> useDNSServers; 117 std::optional<bool> useNTPServers; 118 std::optional<bool> useUseDomainName; 119 std::optional<std::string> dhcpv6OperatingMode; 120 }; 121 122 // Helper function that changes bits netmask notation (i.e. /24) 123 // into full dot notation 124 inline std::string getNetmask(unsigned int bits) 125 { 126 uint32_t value = 0xffffffff << (32 - bits); 127 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 128 std::to_string((value >> 16) & 0xff) + "." + 129 std::to_string((value >> 8) & 0xff) + "." + 130 std::to_string(value & 0xff); 131 return netmask; 132 } 133 134 inline bool translateDHCPEnabledToBool(const std::string &inputDHCP, 135 bool isIPv4) 136 { 137 if (isIPv4) 138 { 139 return ( 140 (inputDHCP == 141 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4") || 142 (inputDHCP == 143 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 144 } 145 return ((inputDHCP == 146 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6") || 147 (inputDHCP == 148 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 149 } 150 151 inline std::string GetDHCPEnabledEnumeration(bool isIPv4, bool isIPv6) 152 { 153 if (isIPv4 && isIPv6) 154 { 155 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"; 156 } 157 else if (isIPv4) 158 { 159 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4"; 160 } 161 else if (isIPv6) 162 { 163 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6"; 164 } 165 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none"; 166 } 167 168 inline std::string 169 translateAddressOriginDbusToRedfish(const std::string &inputOrigin, 170 bool isIPv4) 171 { 172 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") 173 { 174 return "Static"; 175 } 176 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") 177 { 178 if (isIPv4) 179 { 180 return "IPv4LinkLocal"; 181 } 182 else 183 { 184 return "LinkLocal"; 185 } 186 } 187 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") 188 { 189 if (isIPv4) 190 { 191 return "DHCP"; 192 } 193 else 194 { 195 return "DHCPv6"; 196 } 197 } 198 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") 199 { 200 return "SLAAC"; 201 } 202 return ""; 203 } 204 205 inline bool extractEthernetInterfaceData(const std::string ðiface_id, 206 const GetManagedObjects &dbus_data, 207 EthernetInterfaceData ðData) 208 { 209 bool idFound = false; 210 for (const auto &objpath : dbus_data) 211 { 212 for (const auto &ifacePair : objpath.second) 213 { 214 if (objpath.first == "/xyz/openbmc_project/network/" + ethiface_id) 215 { 216 idFound = true; 217 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 218 { 219 for (const auto &propertyPair : ifacePair.second) 220 { 221 if (propertyPair.first == "MACAddress") 222 { 223 const std::string *mac = 224 std::get_if<std::string>(&propertyPair.second); 225 if (mac != nullptr) 226 { 227 ethData.mac_address = *mac; 228 } 229 } 230 } 231 } 232 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") 233 { 234 for (const auto &propertyPair : ifacePair.second) 235 { 236 if (propertyPair.first == "Id") 237 { 238 const uint32_t *id = 239 std::get_if<uint32_t>(&propertyPair.second); 240 if (id != nullptr) 241 { 242 ethData.vlan_id.push_back(*id); 243 } 244 } 245 } 246 } 247 else if (ifacePair.first == 248 "xyz.openbmc_project.Network.EthernetInterface") 249 { 250 for (const auto &propertyPair : ifacePair.second) 251 { 252 if (propertyPair.first == "AutoNeg") 253 { 254 const bool *auto_neg = 255 std::get_if<bool>(&propertyPair.second); 256 if (auto_neg != nullptr) 257 { 258 ethData.auto_neg = *auto_neg; 259 } 260 } 261 else if (propertyPair.first == "Speed") 262 { 263 const uint32_t *speed = 264 std::get_if<uint32_t>(&propertyPair.second); 265 if (speed != nullptr) 266 { 267 ethData.speed = *speed; 268 } 269 } 270 else if (propertyPair.first == "LinkUp") 271 { 272 const bool *linkUp = 273 std::get_if<bool>(&propertyPair.second); 274 if (linkUp != nullptr) 275 { 276 ethData.linkUp = *linkUp; 277 } 278 } 279 else if (propertyPair.first == "NICEnabled") 280 { 281 const bool *nicEnabled = 282 std::get_if<bool>(&propertyPair.second); 283 if (nicEnabled != nullptr) 284 { 285 ethData.nicEnabled = *nicEnabled; 286 } 287 } 288 else if (propertyPair.first == "Nameservers") 289 { 290 const std::vector<std::string> *nameservers = 291 sdbusplus::message::variant_ns::get_if< 292 std::vector<std::string>>( 293 &propertyPair.second); 294 if (nameservers != nullptr) 295 { 296 ethData.nameservers = std::move(*nameservers); 297 } 298 } 299 else if (propertyPair.first == "DHCPEnabled") 300 { 301 const std::string *DHCPEnabled = 302 std::get_if<std::string>(&propertyPair.second); 303 if (DHCPEnabled != nullptr) 304 { 305 ethData.DHCPEnabled = *DHCPEnabled; 306 } 307 } 308 else if (propertyPair.first == "DomainName") 309 { 310 const std::vector<std::string> *domainNames = 311 sdbusplus::message::variant_ns::get_if< 312 std::vector<std::string>>( 313 &propertyPair.second); 314 if (domainNames != nullptr) 315 { 316 ethData.domainnames = std::move(*domainNames); 317 } 318 } 319 } 320 } 321 } 322 323 if (objpath.first == "/xyz/openbmc_project/network/config/dhcp") 324 { 325 if (ifacePair.first == 326 "xyz.openbmc_project.Network.DHCPConfiguration") 327 { 328 for (const auto &propertyPair : ifacePair.second) 329 { 330 if (propertyPair.first == "DNSEnabled") 331 { 332 const bool *DNSEnabled = 333 std::get_if<bool>(&propertyPair.second); 334 if (DNSEnabled != nullptr) 335 { 336 ethData.DNSEnabled = *DNSEnabled; 337 } 338 } 339 else if (propertyPair.first == "NTPEnabled") 340 { 341 const bool *NTPEnabled = 342 std::get_if<bool>(&propertyPair.second); 343 if (NTPEnabled != nullptr) 344 { 345 ethData.NTPEnabled = *NTPEnabled; 346 } 347 } 348 else if (propertyPair.first == "HostNameEnabled") 349 { 350 const bool *HostNameEnabled = 351 std::get_if<bool>(&propertyPair.second); 352 if (HostNameEnabled != nullptr) 353 { 354 ethData.HostNameEnabled = *HostNameEnabled; 355 } 356 } 357 else if (propertyPair.first == "SendHostNameEnabled") 358 { 359 const bool *SendHostNameEnabled = 360 std::get_if<bool>(&propertyPair.second); 361 if (SendHostNameEnabled != nullptr) 362 { 363 ethData.SendHostNameEnabled = 364 *SendHostNameEnabled; 365 } 366 } 367 } 368 } 369 } 370 // System configuration shows up in the global namespace, so no need 371 // to check eth number 372 if (ifacePair.first == 373 "xyz.openbmc_project.Network.SystemConfiguration") 374 { 375 for (const auto &propertyPair : ifacePair.second) 376 { 377 if (propertyPair.first == "HostName") 378 { 379 const std::string *hostname = 380 sdbusplus::message::variant_ns::get_if<std::string>( 381 &propertyPair.second); 382 if (hostname != nullptr) 383 { 384 ethData.hostname = *hostname; 385 } 386 } 387 else if (propertyPair.first == "DefaultGateway") 388 { 389 const std::string *defaultGateway = 390 sdbusplus::message::variant_ns::get_if<std::string>( 391 &propertyPair.second); 392 if (defaultGateway != nullptr) 393 { 394 ethData.default_gateway = *defaultGateway; 395 } 396 } 397 else if (propertyPair.first == "DefaultGateway6") 398 { 399 const std::string *defaultGateway6 = 400 sdbusplus::message::variant_ns::get_if<std::string>( 401 &propertyPair.second); 402 if (defaultGateway6 != nullptr) 403 { 404 ethData.ipv6_default_gateway = *defaultGateway6; 405 } 406 } 407 } 408 } 409 } 410 } 411 return idFound; 412 } 413 414 // Helper function that extracts data for single ethernet ipv6 address 415 inline void 416 extractIPV6Data(const std::string ðiface_id, 417 const GetManagedObjects &dbus_data, 418 boost::container::flat_set<IPv6AddressData> &ipv6_config) 419 { 420 const std::string ipv6PathStart = 421 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv6/"; 422 423 // Since there might be several IPv6 configurations aligned with 424 // single ethernet interface, loop over all of them 425 for (const auto &objpath : dbus_data) 426 { 427 // Check if proper pattern for object path appears 428 if (boost::starts_with(objpath.first.str, ipv6PathStart)) 429 { 430 for (auto &interface : objpath.second) 431 { 432 if (interface.first == "xyz.openbmc_project.Network.IP") 433 { 434 // Instance IPv6AddressData structure, and set as 435 // appropriate 436 std::pair< 437 boost::container::flat_set<IPv6AddressData>::iterator, 438 bool> 439 it = ipv6_config.insert(IPv6AddressData{}); 440 IPv6AddressData &ipv6_address = *it.first; 441 ipv6_address.id = 442 objpath.first.str.substr(ipv6PathStart.size()); 443 for (auto &property : interface.second) 444 { 445 if (property.first == "Address") 446 { 447 const std::string *address = 448 std::get_if<std::string>(&property.second); 449 if (address != nullptr) 450 { 451 ipv6_address.address = *address; 452 } 453 } 454 else if (property.first == "Origin") 455 { 456 const std::string *origin = 457 std::get_if<std::string>(&property.second); 458 if (origin != nullptr) 459 { 460 ipv6_address.origin = 461 translateAddressOriginDbusToRedfish(*origin, 462 false); 463 } 464 } 465 else if (property.first == "PrefixLength") 466 { 467 const uint8_t *prefix = 468 std::get_if<uint8_t>(&property.second); 469 if (prefix != nullptr) 470 { 471 ipv6_address.prefixLength = *prefix; 472 } 473 } 474 else 475 { 476 BMCWEB_LOG_ERROR 477 << "Got extra property: " << property.first 478 << " on the " << objpath.first.str << " object"; 479 } 480 } 481 } 482 } 483 } 484 } 485 } 486 487 // Helper function that extracts data for single ethernet ipv4 address 488 inline void 489 extractIPData(const std::string ðiface_id, 490 const GetManagedObjects &dbus_data, 491 boost::container::flat_set<IPv4AddressData> &ipv4_config) 492 { 493 const std::string ipv4PathStart = 494 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/"; 495 496 // Since there might be several IPv4 configurations aligned with 497 // single ethernet interface, loop over all of them 498 for (const auto &objpath : dbus_data) 499 { 500 // Check if proper pattern for object path appears 501 if (boost::starts_with(objpath.first.str, ipv4PathStart)) 502 { 503 for (auto &interface : objpath.second) 504 { 505 if (interface.first == "xyz.openbmc_project.Network.IP") 506 { 507 // Instance IPv4AddressData structure, and set as 508 // appropriate 509 std::pair< 510 boost::container::flat_set<IPv4AddressData>::iterator, 511 bool> 512 it = ipv4_config.insert(IPv4AddressData{}); 513 IPv4AddressData &ipv4_address = *it.first; 514 ipv4_address.id = 515 objpath.first.str.substr(ipv4PathStart.size()); 516 for (auto &property : interface.second) 517 { 518 if (property.first == "Address") 519 { 520 const std::string *address = 521 std::get_if<std::string>(&property.second); 522 if (address != nullptr) 523 { 524 ipv4_address.address = *address; 525 } 526 } 527 else if (property.first == "Gateway") 528 { 529 const std::string *gateway = 530 std::get_if<std::string>(&property.second); 531 if (gateway != nullptr) 532 { 533 ipv4_address.gateway = *gateway; 534 } 535 } 536 else if (property.first == "Origin") 537 { 538 const std::string *origin = 539 std::get_if<std::string>(&property.second); 540 if (origin != nullptr) 541 { 542 ipv4_address.origin = 543 translateAddressOriginDbusToRedfish(*origin, 544 true); 545 } 546 } 547 else if (property.first == "PrefixLength") 548 { 549 const uint8_t *mask = 550 std::get_if<uint8_t>(&property.second); 551 if (mask != nullptr) 552 { 553 // convert it to the string 554 ipv4_address.netmask = getNetmask(*mask); 555 } 556 } 557 else 558 { 559 BMCWEB_LOG_ERROR 560 << "Got extra property: " << property.first 561 << " on the " << objpath.first.str << " object"; 562 } 563 } 564 // Check if given address is local, or global 565 ipv4_address.linktype = 566 boost::starts_with(ipv4_address.address, "169.254.") 567 ? LinkType::Local 568 : LinkType::Global; 569 } 570 } 571 } 572 } 573 } 574 575 /** 576 * @brief Sets given Id on the given VLAN interface through D-Bus 577 * 578 * @param[in] ifaceId Id of VLAN interface that should be modified 579 * @param[in] inputVlanId New ID of the VLAN 580 * @param[in] callback Function that will be called after the operation 581 * 582 * @return None. 583 */ 584 template <typename CallbackFunc> 585 void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId, 586 CallbackFunc &&callback) 587 { 588 crow::connections::systemBus->async_method_call( 589 callback, "xyz.openbmc_project.Network", 590 std::string("/xyz/openbmc_project/network/") + ifaceId, 591 "org.freedesktop.DBus.Properties", "Set", 592 "xyz.openbmc_project.Network.VLAN", "Id", 593 std::variant<uint32_t>(inputVlanId)); 594 } 595 596 /** 597 * @brief Helper function that verifies IP address to check if it is in 598 * proper format. If bits pointer is provided, also calculates active 599 * bit count for Subnet Mask. 600 * 601 * @param[in] ip IP that will be verified 602 * @param[out] bits Calculated mask in bits notation 603 * 604 * @return true in case of success, false otherwise 605 */ 606 inline bool ipv4VerifyIpAndGetBitcount(const std::string &ip, 607 uint8_t *bits = nullptr) 608 { 609 std::vector<std::string> bytesInMask; 610 611 boost::split(bytesInMask, ip, boost::is_any_of(".")); 612 613 static const constexpr int ipV4AddressSectionsCount = 4; 614 if (bytesInMask.size() != ipV4AddressSectionsCount) 615 { 616 return false; 617 } 618 619 if (bits != nullptr) 620 { 621 *bits = 0; 622 } 623 624 char *endPtr; 625 long previousValue = 255; 626 bool firstZeroInByteHit; 627 for (const std::string &byte : bytesInMask) 628 { 629 if (byte.empty()) 630 { 631 return false; 632 } 633 634 // Use strtol instead of stroi to avoid exceptions 635 long value = std::strtol(byte.c_str(), &endPtr, 10); 636 637 // endPtr should point to the end of the string, otherwise given string 638 // is not 100% number 639 if (*endPtr != '\0') 640 { 641 return false; 642 } 643 644 // Value should be contained in byte 645 if (value < 0 || value > 255) 646 { 647 return false; 648 } 649 650 if (bits != nullptr) 651 { 652 // Mask has to be continuous between bytes 653 if (previousValue != 255 && value != 0) 654 { 655 return false; 656 } 657 658 // Mask has to be continuous inside bytes 659 firstZeroInByteHit = false; 660 661 // Count bits 662 for (int bitIdx = 7; bitIdx >= 0; bitIdx--) 663 { 664 if (value & (1 << bitIdx)) 665 { 666 if (firstZeroInByteHit) 667 { 668 // Continuity not preserved 669 return false; 670 } 671 else 672 { 673 (*bits)++; 674 } 675 } 676 else 677 { 678 firstZeroInByteHit = true; 679 } 680 } 681 } 682 683 previousValue = value; 684 } 685 686 return true; 687 } 688 689 /** 690 * @brief Deletes given IPv4 interface 691 * 692 * @param[in] ifaceId Id of interface whose IP should be deleted 693 * @param[in] ipHash DBus Hash id of IP that should be deleted 694 * @param[io] asyncResp Response object that will be returned to client 695 * 696 * @return None 697 */ 698 inline void deleteIPv4(const std::string &ifaceId, const std::string &ipHash, 699 const std::shared_ptr<AsyncResp> asyncResp) 700 { 701 crow::connections::systemBus->async_method_call( 702 [asyncResp](const boost::system::error_code ec) { 703 if (ec) 704 { 705 messages::internalError(asyncResp->res); 706 } 707 }, 708 "xyz.openbmc_project.Network", 709 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 710 "xyz.openbmc_project.Object.Delete", "Delete"); 711 } 712 713 /** 714 * @brief Creates a static IPv4 entry 715 * 716 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 717 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 718 * @param[in] gateway IPv4 address of this interfaces gateway 719 * @param[in] address IPv4 address to assign to this interface 720 * @param[io] asyncResp Response object that will be returned to client 721 * 722 * @return None 723 */ 724 inline void createIPv4(const std::string &ifaceId, unsigned int ipIdx, 725 uint8_t prefixLength, const std::string &gateway, 726 const std::string &address, 727 std::shared_ptr<AsyncResp> asyncResp) 728 { 729 crow::connections::systemBus->async_method_call( 730 [asyncResp](const boost::system::error_code ec) { 731 if (ec) 732 { 733 messages::internalError(asyncResp->res); 734 } 735 }, 736 "xyz.openbmc_project.Network", 737 "/xyz/openbmc_project/network/" + ifaceId, 738 "xyz.openbmc_project.Network.IP.Create", "IP", 739 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, 740 gateway); 741 } 742 743 /** 744 * @brief Deletes the IPv4 entry for this interface and creates a replacement 745 * static IPv4 entry 746 * 747 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 748 * @param[in] id The unique hash entry identifying the DBus entry 749 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 750 * @param[in] gateway IPv4 address of this interfaces gateway 751 * @param[in] address IPv4 address to assign to this interface 752 * @param[io] asyncResp Response object that will be returned to client 753 * 754 * @return None 755 */ 756 inline void deleteAndCreateIPv4(const std::string &ifaceId, 757 const std::string &id, uint8_t prefixLength, 758 const std::string &gateway, 759 const std::string &address, 760 std::shared_ptr<AsyncResp> asyncResp) 761 { 762 crow::connections::systemBus->async_method_call( 763 [asyncResp, ifaceId, address, prefixLength, 764 gateway](const boost::system::error_code ec) { 765 if (ec) 766 { 767 messages::internalError(asyncResp->res); 768 } 769 crow::connections::systemBus->async_method_call( 770 [asyncResp](const boost::system::error_code ec) { 771 if (ec) 772 { 773 messages::internalError(asyncResp->res); 774 } 775 }, 776 "xyz.openbmc_project.Network", 777 "/xyz/openbmc_project/network/" + ifaceId, 778 "xyz.openbmc_project.Network.IP.Create", "IP", 779 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, 780 prefixLength, gateway); 781 }, 782 "xyz.openbmc_project.Network", 783 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id, 784 "xyz.openbmc_project.Object.Delete", "Delete"); 785 } 786 787 /** 788 * @brief Deletes given IPv6 789 * 790 * @param[in] ifaceId Id of interface whose IP should be deleted 791 * @param[in] ipHash DBus Hash id of IP that should be deleted 792 * @param[io] asyncResp Response object that will be returned to client 793 * 794 * @return None 795 */ 796 inline void deleteIPv6(const std::string &ifaceId, const std::string &ipHash, 797 const std::shared_ptr<AsyncResp> asyncResp) 798 { 799 crow::connections::systemBus->async_method_call( 800 [asyncResp](const boost::system::error_code ec) { 801 if (ec) 802 { 803 messages::internalError(asyncResp->res); 804 } 805 }, 806 "xyz.openbmc_project.Network", 807 "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash, 808 "xyz.openbmc_project.Object.Delete", "Delete"); 809 } 810 811 /** 812 * @brief Deletes the IPv6 entry for this interface and creates a replacement 813 * static IPv6 entry 814 * 815 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 816 * @param[in] id The unique hash entry identifying the DBus entry 817 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 818 * @param[in] address IPv6 address to assign to this interface 819 * @param[io] asyncResp Response object that will be returned to client 820 * 821 * @return None 822 */ 823 inline void deleteAndCreateIPv6(const std::string &ifaceId, 824 const std::string &id, uint8_t prefixLength, 825 const std::string &address, 826 std::shared_ptr<AsyncResp> asyncResp) 827 { 828 crow::connections::systemBus->async_method_call( 829 [asyncResp, ifaceId, address, 830 prefixLength](const boost::system::error_code ec) { 831 if (ec) 832 { 833 messages::internalError(asyncResp->res); 834 } 835 crow::connections::systemBus->async_method_call( 836 [asyncResp](const boost::system::error_code ec) { 837 if (ec) 838 { 839 messages::internalError(asyncResp->res); 840 } 841 }, 842 "xyz.openbmc_project.Network", 843 "/xyz/openbmc_project/network/" + ifaceId, 844 "xyz.openbmc_project.Network.IP.Create", "IP", 845 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, 846 prefixLength, ""); 847 }, 848 "xyz.openbmc_project.Network", 849 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id, 850 "xyz.openbmc_project.Object.Delete", "Delete"); 851 } 852 853 /** 854 * @brief Creates IPv6 with given data 855 * 856 * @param[in] ifaceId Id of interface whose IP should be added 857 * @param[in] prefixLength Prefix length that needs to be added 858 * @param[in] address IP address that needs to be added 859 * @param[io] asyncResp Response object that will be returned to client 860 * 861 * @return None 862 */ 863 inline void createIPv6(const std::string &ifaceId, uint8_t prefixLength, 864 const std::string &address, 865 std::shared_ptr<AsyncResp> asyncResp) 866 { 867 auto createIpHandler = [asyncResp](const boost::system::error_code ec) { 868 if (ec) 869 { 870 messages::internalError(asyncResp->res); 871 } 872 }; 873 // Passing null for gateway, as per redfish spec IPv6StaticAddresses object 874 // does not have assosiated gateway property 875 crow::connections::systemBus->async_method_call( 876 std::move(createIpHandler), "xyz.openbmc_project.Network", 877 "/xyz/openbmc_project/network/" + ifaceId, 878 "xyz.openbmc_project.Network.IP.Create", "IP", 879 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 880 ""); 881 } 882 883 /** 884 * Function that retrieves all properties for given Ethernet Interface 885 * Object 886 * from EntityManager Network Manager 887 * @param ethiface_id a eth interface id to query on DBus 888 * @param callback a function that shall be called to convert Dbus output 889 * into JSON 890 */ 891 template <typename CallbackFunc> 892 void getEthernetIfaceData(const std::string ðiface_id, 893 CallbackFunc &&callback) 894 { 895 crow::connections::systemBus->async_method_call( 896 [ethiface_id{std::string{ethiface_id}}, callback{std::move(callback)}]( 897 const boost::system::error_code error_code, 898 const GetManagedObjects &resp) { 899 EthernetInterfaceData ethData{}; 900 boost::container::flat_set<IPv4AddressData> ipv4Data; 901 boost::container::flat_set<IPv6AddressData> ipv6Data; 902 903 if (error_code) 904 { 905 callback(false, ethData, ipv4Data, ipv6Data); 906 return; 907 } 908 909 bool found = 910 extractEthernetInterfaceData(ethiface_id, resp, ethData); 911 if (!found) 912 { 913 callback(false, ethData, ipv4Data, ipv6Data); 914 return; 915 } 916 917 extractIPData(ethiface_id, resp, ipv4Data); 918 // Fix global GW 919 for (IPv4AddressData &ipv4 : ipv4Data) 920 { 921 if (((ipv4.linktype == LinkType::Global) && 922 (ipv4.gateway == "0.0.0.0")) || 923 (ipv4.origin == "DHCP")) 924 { 925 ipv4.gateway = ethData.default_gateway; 926 } 927 } 928 929 extractIPV6Data(ethiface_id, resp, ipv6Data); 930 // Finally make a callback with usefull data 931 callback(true, ethData, ipv4Data, ipv6Data); 932 }, 933 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 934 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 935 } 936 937 /** 938 * Function that retrieves all Ethernet Interfaces available through Network 939 * Manager 940 * @param callback a function that shall be called to convert Dbus output 941 * into JSON. 942 */ 943 template <typename CallbackFunc> 944 void getEthernetIfaceList(CallbackFunc &&callback) 945 { 946 crow::connections::systemBus->async_method_call( 947 [callback{std::move(callback)}]( 948 const boost::system::error_code error_code, 949 GetManagedObjects &resp) { 950 // Callback requires vector<string> to retrieve all available 951 // ethernet interfaces 952 boost::container::flat_set<std::string> iface_list; 953 iface_list.reserve(resp.size()); 954 if (error_code) 955 { 956 callback(false, iface_list); 957 return; 958 } 959 960 // Iterate over all retrieved ObjectPaths. 961 for (const auto &objpath : resp) 962 { 963 // And all interfaces available for certain ObjectPath. 964 for (const auto &interface : objpath.second) 965 { 966 // If interface is 967 // xyz.openbmc_project.Network.EthernetInterface, this is 968 // what we're looking for. 969 if (interface.first == 970 "xyz.openbmc_project.Network.EthernetInterface") 971 { 972 // Cut out everyting until last "/", ... 973 const std::string &iface_id = objpath.first.str; 974 std::size_t last_pos = iface_id.rfind("/"); 975 if (last_pos != std::string::npos) 976 { 977 // and put it into output vector. 978 iface_list.emplace(iface_id.substr(last_pos + 1)); 979 } 980 } 981 } 982 } 983 // Finally make a callback with useful data 984 callback(true, iface_list); 985 }, 986 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 987 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 988 } 989 990 /** 991 * EthernetCollection derived class for delivering Ethernet Collection Schema 992 */ 993 class EthernetCollection : public Node 994 { 995 public: 996 template <typename CrowApp> 997 EthernetCollection(CrowApp &app) : 998 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 999 { 1000 entityPrivileges = { 1001 {boost::beast::http::verb::get, {{"Login"}}}, 1002 {boost::beast::http::verb::head, {{"Login"}}}, 1003 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1004 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1005 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1006 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1007 } 1008 1009 private: 1010 /** 1011 * Functions triggers appropriate requests on DBus 1012 */ 1013 void doGet(crow::Response &res, const crow::Request &req, 1014 const std::vector<std::string> ¶ms) override 1015 { 1016 res.jsonValue["@odata.type"] = 1017 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 1018 res.jsonValue["@odata.id"] = 1019 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1020 res.jsonValue["Name"] = "Ethernet Network Interface Collection"; 1021 res.jsonValue["Description"] = 1022 "Collection of EthernetInterfaces for this Manager"; 1023 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1024 // Get eth interface list, and call the below callback for JSON 1025 // preparation 1026 getEthernetIfaceList( 1027 [asyncResp]( 1028 const bool &success, 1029 const boost::container::flat_set<std::string> &iface_list) { 1030 if (!success) 1031 { 1032 messages::internalError(asyncResp->res); 1033 return; 1034 } 1035 1036 nlohmann::json &iface_array = 1037 asyncResp->res.jsonValue["Members"]; 1038 iface_array = nlohmann::json::array(); 1039 std::string tag = "_"; 1040 for (const std::string &iface_item : iface_list) 1041 { 1042 std::size_t found = iface_item.find(tag); 1043 if (found == std::string::npos) 1044 { 1045 iface_array.push_back( 1046 {{"@odata.id", 1047 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1048 iface_item}}); 1049 } 1050 } 1051 1052 asyncResp->res.jsonValue["Members@odata.count"] = 1053 iface_array.size(); 1054 asyncResp->res.jsonValue["@odata.id"] = 1055 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1056 }); 1057 } 1058 }; 1059 1060 /** 1061 * EthernetInterface derived class for delivering Ethernet Schema 1062 */ 1063 class EthernetInterface : public Node 1064 { 1065 public: 1066 /* 1067 * Default Constructor 1068 */ 1069 template <typename CrowApp> 1070 EthernetInterface(CrowApp &app) : 1071 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/", 1072 std::string()) 1073 { 1074 entityPrivileges = { 1075 {boost::beast::http::verb::get, {{"Login"}}}, 1076 {boost::beast::http::verb::head, {{"Login"}}}, 1077 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1078 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1079 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1080 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1081 } 1082 1083 private: 1084 void handleHostnamePatch(const std::string &hostname, 1085 const std::shared_ptr<AsyncResp> asyncResp) 1086 { 1087 asyncResp->res.jsonValue["HostName"] = hostname; 1088 crow::connections::systemBus->async_method_call( 1089 [asyncResp](const boost::system::error_code ec) { 1090 if (ec) 1091 { 1092 messages::internalError(asyncResp->res); 1093 } 1094 }, 1095 "xyz.openbmc_project.Network", 1096 "/xyz/openbmc_project/network/config", 1097 "org.freedesktop.DBus.Properties", "Set", 1098 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 1099 std::variant<std::string>(hostname)); 1100 } 1101 1102 void handleMACAddressPatch(const std::string &ifaceId, 1103 const std::string &macAddress, 1104 const std::shared_ptr<AsyncResp> &asyncResp) 1105 { 1106 crow::connections::systemBus->async_method_call( 1107 [asyncResp, macAddress](const boost::system::error_code ec) { 1108 if (ec) 1109 { 1110 messages::internalError(asyncResp->res); 1111 return; 1112 } 1113 }, 1114 "xyz.openbmc_project.Network", 1115 "/xyz/openbmc_project/network/" + ifaceId, 1116 "org.freedesktop.DBus.Properties", "Set", 1117 "xyz.openbmc_project.Network.MACAddress", "MACAddress", 1118 std::variant<std::string>(macAddress)); 1119 } 1120 1121 void setDHCPEnabled(const std::string &ifaceId, 1122 const std::string &propertyName, const bool v4Value, 1123 const bool v6Value, 1124 const std::shared_ptr<AsyncResp> asyncResp) 1125 { 1126 const std::string dhcp = GetDHCPEnabledEnumeration(v4Value, v6Value); 1127 crow::connections::systemBus->async_method_call( 1128 [asyncResp](const boost::system::error_code ec) { 1129 if (ec) 1130 { 1131 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1132 messages::internalError(asyncResp->res); 1133 return; 1134 } 1135 }, 1136 "xyz.openbmc_project.Network", 1137 "/xyz/openbmc_project/network/" + ifaceId, 1138 "org.freedesktop.DBus.Properties", "Set", 1139 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1140 std::variant<std::string>{dhcp}); 1141 } 1142 1143 void setEthernetInterfaceBoolProperty( 1144 const std::string &ifaceId, const std::string &propertyName, 1145 const bool &value, const std::shared_ptr<AsyncResp> asyncResp) 1146 { 1147 crow::connections::systemBus->async_method_call( 1148 [asyncResp](const boost::system::error_code ec) { 1149 if (ec) 1150 { 1151 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1152 messages::internalError(asyncResp->res); 1153 return; 1154 } 1155 }, 1156 "xyz.openbmc_project.Network", 1157 "/xyz/openbmc_project/network/" + ifaceId, 1158 "org.freedesktop.DBus.Properties", "Set", 1159 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1160 std::variant<bool>{value}); 1161 } 1162 1163 void setDHCPv4Config(const std::string &propertyName, const bool &value, 1164 const std::shared_ptr<AsyncResp> asyncResp) 1165 { 1166 BMCWEB_LOG_DEBUG << propertyName << " = " << value; 1167 crow::connections::systemBus->async_method_call( 1168 [asyncResp](const boost::system::error_code ec) { 1169 if (ec) 1170 { 1171 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1172 messages::internalError(asyncResp->res); 1173 return; 1174 } 1175 }, 1176 "xyz.openbmc_project.Network", 1177 "/xyz/openbmc_project/network/config/dhcp", 1178 "org.freedesktop.DBus.Properties", "Set", 1179 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, 1180 std::variant<bool>{value}); 1181 } 1182 1183 void handleDHCPPatch(const std::string &ifaceId, 1184 const EthernetInterfaceData ðData, 1185 DHCPParameters v4dhcpParms, DHCPParameters v6dhcpParms, 1186 const std::shared_ptr<AsyncResp> asyncResp) 1187 { 1188 bool ipv4Active = translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 1189 bool ipv6Active = 1190 translateDHCPEnabledToBool(ethData.DHCPEnabled, false); 1191 1192 bool nextv4DHCPState = 1193 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1194 1195 bool nextv6DHCPState{}; 1196 if (v6dhcpParms.dhcpv6OperatingMode) 1197 { 1198 if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") && 1199 (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") && 1200 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1201 { 1202 messages::propertyValueFormatError( 1203 asyncResp->res, *v6dhcpParms.dhcpv6OperatingMode, 1204 "OperatingMode"); 1205 return; 1206 } 1207 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful"); 1208 } 1209 else 1210 { 1211 nextv6DHCPState = ipv6Active; 1212 } 1213 1214 bool nextDNS{}; 1215 if (v4dhcpParms.useDNSServers && v6dhcpParms.useDNSServers) 1216 { 1217 if (*v4dhcpParms.useDNSServers != *v6dhcpParms.useDNSServers) 1218 { 1219 messages::generalError(asyncResp->res); 1220 return; 1221 } 1222 nextDNS = *v4dhcpParms.useDNSServers; 1223 } 1224 else if (v4dhcpParms.useDNSServers) 1225 { 1226 nextDNS = *v4dhcpParms.useDNSServers; 1227 } 1228 else if (v6dhcpParms.useDNSServers) 1229 { 1230 nextDNS = *v6dhcpParms.useDNSServers; 1231 } 1232 else 1233 { 1234 nextDNS = ethData.DNSEnabled; 1235 } 1236 1237 bool nextNTP{}; 1238 if (v4dhcpParms.useNTPServers && v6dhcpParms.useNTPServers) 1239 { 1240 if (*v4dhcpParms.useNTPServers != *v6dhcpParms.useNTPServers) 1241 { 1242 messages::generalError(asyncResp->res); 1243 return; 1244 } 1245 nextNTP = *v4dhcpParms.useNTPServers; 1246 } 1247 else if (v4dhcpParms.useNTPServers) 1248 { 1249 nextNTP = *v4dhcpParms.useNTPServers; 1250 } 1251 else if (v6dhcpParms.useNTPServers) 1252 { 1253 nextNTP = *v6dhcpParms.useNTPServers; 1254 } 1255 else 1256 { 1257 nextNTP = ethData.NTPEnabled; 1258 } 1259 1260 bool nextUseDomain{}; 1261 if (v4dhcpParms.useUseDomainName && v6dhcpParms.useUseDomainName) 1262 { 1263 if (*v4dhcpParms.useUseDomainName != *v6dhcpParms.useUseDomainName) 1264 { 1265 messages::generalError(asyncResp->res); 1266 return; 1267 } 1268 nextUseDomain = *v4dhcpParms.useUseDomainName; 1269 } 1270 else if (v4dhcpParms.useUseDomainName) 1271 { 1272 nextUseDomain = *v4dhcpParms.useUseDomainName; 1273 } 1274 else if (v6dhcpParms.useUseDomainName) 1275 { 1276 nextUseDomain = *v6dhcpParms.useUseDomainName; 1277 } 1278 else 1279 { 1280 nextUseDomain = ethData.HostNameEnabled; 1281 } 1282 1283 BMCWEB_LOG_DEBUG << "set DHCPEnabled..."; 1284 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1285 asyncResp); 1286 BMCWEB_LOG_DEBUG << "set DNSEnabled..."; 1287 setDHCPv4Config("DNSEnabled", nextDNS, asyncResp); 1288 BMCWEB_LOG_DEBUG << "set NTPEnabled..."; 1289 setDHCPv4Config("NTPEnabled", nextNTP, asyncResp); 1290 BMCWEB_LOG_DEBUG << "set HostNameEnabled..."; 1291 setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp); 1292 } 1293 1294 boost::container::flat_set<IPv4AddressData>::const_iterator 1295 GetNextStaticIPEntry( 1296 boost::container::flat_set<IPv4AddressData>::const_iterator head, 1297 boost::container::flat_set<IPv4AddressData>::const_iterator end) 1298 { 1299 for (; head != end; head++) 1300 { 1301 if (head->origin == "Static") 1302 { 1303 return head; 1304 } 1305 } 1306 return end; 1307 } 1308 1309 boost::container::flat_set<IPv6AddressData>::const_iterator 1310 GetNextStaticIPEntry( 1311 boost::container::flat_set<IPv6AddressData>::const_iterator head, 1312 boost::container::flat_set<IPv6AddressData>::const_iterator end) 1313 { 1314 for (; head != end; head++) 1315 { 1316 if (head->origin == "Static") 1317 { 1318 return head; 1319 } 1320 } 1321 return end; 1322 } 1323 1324 void handleIPv4StaticPatch( 1325 const std::string &ifaceId, nlohmann::json &input, 1326 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1327 const std::shared_ptr<AsyncResp> asyncResp) 1328 { 1329 if ((!input.is_array()) || input.empty()) 1330 { 1331 messages::propertyValueTypeError(asyncResp->res, input.dump(), 1332 "IPv4StaticAddresses"); 1333 return; 1334 } 1335 1336 unsigned entryIdx = 1; 1337 // Find the first static IP address currently active on the NIC and 1338 // match it to the first JSON element in the IPv4StaticAddresses array. 1339 // Match each subsequent JSON element to the next static IP programmed 1340 // into the NIC. 1341 boost::container::flat_set<IPv4AddressData>::const_iterator NICIPentry = 1342 GetNextStaticIPEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1343 1344 for (nlohmann::json &thisJson : input) 1345 { 1346 std::string pathString = 1347 "IPv4StaticAddresses/" + std::to_string(entryIdx); 1348 1349 if (!thisJson.is_null() && !thisJson.empty()) 1350 { 1351 std::optional<std::string> address; 1352 std::optional<std::string> subnetMask; 1353 std::optional<std::string> gateway; 1354 1355 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1356 address, "SubnetMask", subnetMask, 1357 "Gateway", gateway)) 1358 { 1359 messages::propertyValueFormatError( 1360 asyncResp->res, thisJson.dump(), pathString); 1361 return; 1362 } 1363 1364 // Find the address/subnet/gateway values. Any values that are 1365 // not explicitly provided are assumed to be unmodified from the 1366 // current state of the interface. Merge existing state into the 1367 // current request. 1368 const std::string *addr = nullptr; 1369 const std::string *gw = nullptr; 1370 uint8_t prefixLength = 0; 1371 bool errorInEntry = false; 1372 if (address) 1373 { 1374 if (ipv4VerifyIpAndGetBitcount(*address)) 1375 { 1376 addr = &(*address); 1377 } 1378 else 1379 { 1380 messages::propertyValueFormatError( 1381 asyncResp->res, *address, pathString + "/Address"); 1382 errorInEntry = true; 1383 } 1384 } 1385 else if (NICIPentry != ipv4Data.cend()) 1386 { 1387 addr = &(NICIPentry->address); 1388 } 1389 else 1390 { 1391 messages::propertyMissing(asyncResp->res, 1392 pathString + "/Address"); 1393 errorInEntry = true; 1394 } 1395 1396 if (subnetMask) 1397 { 1398 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) 1399 { 1400 messages::propertyValueFormatError( 1401 asyncResp->res, *subnetMask, 1402 pathString + "/SubnetMask"); 1403 errorInEntry = true; 1404 } 1405 } 1406 else if (NICIPentry != ipv4Data.cend()) 1407 { 1408 if (!ipv4VerifyIpAndGetBitcount(NICIPentry->netmask, 1409 &prefixLength)) 1410 { 1411 messages::propertyValueFormatError( 1412 asyncResp->res, NICIPentry->netmask, 1413 pathString + "/SubnetMask"); 1414 errorInEntry = true; 1415 } 1416 } 1417 else 1418 { 1419 messages::propertyMissing(asyncResp->res, 1420 pathString + "/SubnetMask"); 1421 errorInEntry = true; 1422 } 1423 1424 if (gateway) 1425 { 1426 if (ipv4VerifyIpAndGetBitcount(*gateway)) 1427 { 1428 gw = &(*gateway); 1429 } 1430 else 1431 { 1432 messages::propertyValueFormatError( 1433 asyncResp->res, *gateway, pathString + "/Gateway"); 1434 errorInEntry = true; 1435 } 1436 } 1437 else if (NICIPentry != ipv4Data.cend()) 1438 { 1439 gw = &NICIPentry->gateway; 1440 } 1441 else 1442 { 1443 messages::propertyMissing(asyncResp->res, 1444 pathString + "/Gateway"); 1445 errorInEntry = true; 1446 } 1447 1448 if (errorInEntry) 1449 { 1450 return; 1451 } 1452 1453 if (NICIPentry != ipv4Data.cend()) 1454 { 1455 if (gw != nullptr || addr != nullptr) 1456 { 1457 // Shouldn't be possible based on errorInEntry, but 1458 // it flags -wmaybe-uninitialized in the compiler, 1459 // so defend against that 1460 return; 1461 } 1462 deleteAndCreateIPv4(ifaceId, NICIPentry->id, prefixLength, 1463 *gw, *addr, asyncResp); 1464 NICIPentry = 1465 GetNextStaticIPEntry(++NICIPentry, ipv4Data.cend()); 1466 } 1467 else 1468 { 1469 createIPv4(ifaceId, entryIdx, prefixLength, *gateway, 1470 *address, asyncResp); 1471 } 1472 entryIdx++; 1473 } 1474 else 1475 { 1476 if (NICIPentry == ipv4Data.cend()) 1477 { 1478 // Requesting a DELETE/DO NOT MODIFY action for an item 1479 // that isn't present on the eth(n) interface. Input JSON is 1480 // in error, so bail out. 1481 if (thisJson.is_null()) 1482 { 1483 messages::resourceCannotBeDeleted(asyncResp->res); 1484 return; 1485 } 1486 else 1487 { 1488 messages::propertyValueFormatError( 1489 asyncResp->res, thisJson.dump(), pathString); 1490 return; 1491 } 1492 } 1493 1494 if (thisJson.is_null()) 1495 { 1496 deleteIPv4(ifaceId, NICIPentry->id, asyncResp); 1497 } 1498 if (NICIPentry != ipv4Data.cend()) 1499 { 1500 NICIPentry = 1501 GetNextStaticIPEntry(++NICIPentry, ipv4Data.cend()); 1502 } 1503 entryIdx++; 1504 } 1505 } 1506 } 1507 1508 void handleStaticNameServersPatch( 1509 const std::string &ifaceId, 1510 const std::vector<std::string> &updatedStaticNameServers, 1511 const std::shared_ptr<AsyncResp> &asyncResp) 1512 { 1513 crow::connections::systemBus->async_method_call( 1514 [asyncResp](const boost::system::error_code ec) { 1515 if (ec) 1516 { 1517 messages::internalError(asyncResp->res); 1518 return; 1519 } 1520 }, 1521 "xyz.openbmc_project.Network", 1522 "/xyz/openbmc_project/network/" + ifaceId, 1523 "org.freedesktop.DBus.Properties", "Set", 1524 "xyz.openbmc_project.Network.EthernetInterface", "Nameservers", 1525 std::variant<std::vector<std::string>>{updatedStaticNameServers}); 1526 } 1527 1528 void handleIPv6StaticAddressesPatch( 1529 const std::string &ifaceId, nlohmann::json &input, 1530 const boost::container::flat_set<IPv6AddressData> &ipv6Data, 1531 const std::shared_ptr<AsyncResp> asyncResp) 1532 { 1533 if (!input.is_array() || input.empty()) 1534 { 1535 messages::propertyValueTypeError(asyncResp->res, input.dump(), 1536 "IPv6StaticAddresses"); 1537 return; 1538 } 1539 size_t entryIdx = 1; 1540 boost::container::flat_set<IPv6AddressData>::const_iterator NICIPentry = 1541 GetNextStaticIPEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1542 for (nlohmann::json &thisJson : input) 1543 { 1544 std::string pathString = 1545 "IPv6StaticAddresses/" + std::to_string(entryIdx); 1546 1547 if (!thisJson.is_null() && !thisJson.empty()) 1548 { 1549 std::optional<std::string> address; 1550 std::optional<uint8_t> prefixLength; 1551 1552 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1553 address, "PrefixLength", prefixLength)) 1554 { 1555 messages::propertyValueFormatError( 1556 asyncResp->res, thisJson.dump(), pathString); 1557 return; 1558 } 1559 1560 const std::string *addr; 1561 uint8_t prefix; 1562 1563 // Find the address and prefixLength values. Any values that are 1564 // not explicitly provided are assumed to be unmodified from the 1565 // current state of the interface. Merge existing state into the 1566 // current request. 1567 if (address) 1568 { 1569 addr = &(*address); 1570 } 1571 else if (NICIPentry != ipv6Data.end()) 1572 { 1573 addr = &(NICIPentry->address); 1574 } 1575 else 1576 { 1577 messages::propertyMissing(asyncResp->res, 1578 pathString + "/Address"); 1579 return; 1580 } 1581 1582 if (prefixLength) 1583 { 1584 prefix = *prefixLength; 1585 } 1586 else if (NICIPentry != ipv6Data.end()) 1587 { 1588 prefix = NICIPentry->prefixLength; 1589 } 1590 else 1591 { 1592 messages::propertyMissing(asyncResp->res, 1593 pathString + "/PrefixLength"); 1594 return; 1595 } 1596 1597 if (NICIPentry != ipv6Data.end()) 1598 { 1599 deleteAndCreateIPv6(ifaceId, NICIPentry->id, prefix, *addr, 1600 asyncResp); 1601 NICIPentry = 1602 GetNextStaticIPEntry(++NICIPentry, ipv6Data.cend()); 1603 } 1604 else 1605 { 1606 createIPv6(ifaceId, *prefixLength, *addr, asyncResp); 1607 } 1608 entryIdx++; 1609 } 1610 else 1611 { 1612 if (NICIPentry == ipv6Data.end()) 1613 { 1614 // Requesting a DELETE/DO NOT MODIFY action for an item 1615 // that isn't present on the eth(n) interface. Input JSON is 1616 // in error, so bail out. 1617 if (thisJson.is_null()) 1618 { 1619 messages::resourceCannotBeDeleted(asyncResp->res); 1620 return; 1621 } 1622 else 1623 { 1624 messages::propertyValueFormatError( 1625 asyncResp->res, thisJson.dump(), pathString); 1626 return; 1627 } 1628 } 1629 1630 if (thisJson.is_null()) 1631 { 1632 deleteIPv6(ifaceId, NICIPentry->id, asyncResp); 1633 } 1634 if (NICIPentry != ipv6Data.cend()) 1635 { 1636 NICIPentry = 1637 GetNextStaticIPEntry(++NICIPentry, ipv6Data.cend()); 1638 } 1639 entryIdx++; 1640 } 1641 } 1642 } 1643 1644 void parseInterfaceData( 1645 std::shared_ptr<AsyncResp> asyncResp, const std::string &iface_id, 1646 const EthernetInterfaceData ðData, 1647 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1648 const boost::container::flat_set<IPv6AddressData> &ipv6Data) 1649 { 1650 constexpr const std::array<const char *, 1> inventoryForEthernet = { 1651 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1652 1653 nlohmann::json &json_response = asyncResp->res.jsonValue; 1654 json_response["Id"] = iface_id; 1655 json_response["@odata.id"] = 1656 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + iface_id; 1657 json_response["InterfaceEnabled"] = ethData.nicEnabled; 1658 1659 auto health = std::make_shared<HealthPopulate>(asyncResp); 1660 1661 crow::connections::systemBus->async_method_call( 1662 [health](const boost::system::error_code ec, 1663 std::vector<std::string> &resp) { 1664 if (ec) 1665 { 1666 return; 1667 } 1668 1669 health->inventory = std::move(resp); 1670 }, 1671 "xyz.openbmc_project.ObjectMapper", 1672 "/xyz/openbmc_project/object_mapper", 1673 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 1674 int32_t(0), inventoryForEthernet); 1675 1676 health->populate(); 1677 1678 if (ethData.nicEnabled) 1679 { 1680 json_response["LinkStatus"] = "LinkUp"; 1681 json_response["Status"]["State"] = "Enabled"; 1682 } 1683 else 1684 { 1685 json_response["LinkStatus"] = "NoLink"; 1686 json_response["Status"]["State"] = "Disabled"; 1687 } 1688 1689 json_response["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1690 json_response["SpeedMbps"] = ethData.speed; 1691 json_response["MACAddress"] = ethData.mac_address; 1692 json_response["DHCPv4"]["DHCPEnabled"] = 1693 translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 1694 json_response["DHCPv4"]["UseNTPServers"] = ethData.NTPEnabled; 1695 json_response["DHCPv4"]["UseDNSServers"] = ethData.DNSEnabled; 1696 json_response["DHCPv4"]["UseDomainName"] = ethData.HostNameEnabled; 1697 1698 json_response["DHCPv6"]["OperatingMode"] = 1699 translateDHCPEnabledToBool(ethData.DHCPEnabled, false) ? "Stateful" 1700 : "Disabled"; 1701 json_response["DHCPv6"]["UseNTPServers"] = ethData.NTPEnabled; 1702 json_response["DHCPv6"]["UseDNSServers"] = ethData.DNSEnabled; 1703 json_response["DHCPv6"]["UseDomainName"] = ethData.HostNameEnabled; 1704 1705 if (!ethData.hostname.empty()) 1706 { 1707 json_response["HostName"] = ethData.hostname; 1708 if (!ethData.domainnames.empty()) 1709 { 1710 json_response["FQDN"] = 1711 ethData.hostname + "." + ethData.domainnames[0]; 1712 } 1713 } 1714 1715 json_response["VLANs"] = { 1716 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1717 iface_id + "/VLANs"}}; 1718 1719 if (translateDHCPEnabledToBool(ethData.DHCPEnabled, true) && 1720 ethData.DNSEnabled) 1721 { 1722 json_response["StaticNameServers"] = nlohmann::json::array(); 1723 } 1724 else 1725 { 1726 json_response["StaticNameServers"] = ethData.nameservers; 1727 } 1728 1729 nlohmann::json &ipv4_array = json_response["IPv4Addresses"]; 1730 nlohmann::json &ipv4_static_array = 1731 json_response["IPv4StaticAddresses"]; 1732 ipv4_array = nlohmann::json::array(); 1733 ipv4_static_array = nlohmann::json::array(); 1734 for (auto &ipv4_config : ipv4Data) 1735 { 1736 1737 std::string gatewayStr = ipv4_config.gateway; 1738 if (gatewayStr.empty()) 1739 { 1740 gatewayStr = "0.0.0.0"; 1741 } 1742 1743 ipv4_array.push_back({{"AddressOrigin", ipv4_config.origin}, 1744 {"SubnetMask", ipv4_config.netmask}, 1745 {"Address", ipv4_config.address}, 1746 {"Gateway", gatewayStr}}); 1747 if (ipv4_config.origin == "Static") 1748 { 1749 ipv4_static_array.push_back( 1750 {{"AddressOrigin", ipv4_config.origin}, 1751 {"SubnetMask", ipv4_config.netmask}, 1752 {"Address", ipv4_config.address}, 1753 {"Gateway", gatewayStr}}); 1754 } 1755 } 1756 1757 json_response["IPv6DefaultGateway"] = ethData.ipv6_default_gateway; 1758 1759 nlohmann::json &ipv6_array = json_response["IPv6Addresses"]; 1760 nlohmann::json &ipv6_static_array = 1761 json_response["IPv6StaticAddresses"]; 1762 ipv6_array = nlohmann::json::array(); 1763 ipv6_static_array = nlohmann::json::array(); 1764 for (auto &ipv6_config : ipv6Data) 1765 { 1766 ipv6_array.push_back({{"Address", ipv6_config.address}, 1767 {"PrefixLength", ipv6_config.prefixLength}, 1768 {"AddressOrigin", ipv6_config.origin}}); 1769 if (ipv6_config.origin == "Static") 1770 { 1771 ipv6_static_array.push_back( 1772 {{"Address", ipv6_config.address}, 1773 {"PrefixLength", ipv6_config.prefixLength}, 1774 {"AddressOrigin", ipv6_config.origin}}); 1775 } 1776 } 1777 } 1778 1779 /** 1780 * Functions triggers appropriate requests on DBus 1781 */ 1782 void doGet(crow::Response &res, const crow::Request &req, 1783 const std::vector<std::string> ¶ms) override 1784 { 1785 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1786 if (params.size() != 1) 1787 { 1788 messages::internalError(asyncResp->res); 1789 return; 1790 } 1791 1792 getEthernetIfaceData( 1793 params[0], 1794 [this, asyncResp, iface_id{std::string(params[0])}]( 1795 const bool &success, const EthernetInterfaceData ðData, 1796 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1797 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 1798 if (!success) 1799 { 1800 // TODO(Pawel)consider distinguish between non existing 1801 // object, and other errors 1802 messages::resourceNotFound(asyncResp->res, 1803 "EthernetInterface", iface_id); 1804 return; 1805 } 1806 1807 asyncResp->res.jsonValue["@odata.type"] = 1808 "#EthernetInterface.v1_4_1.EthernetInterface"; 1809 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1810 asyncResp->res.jsonValue["Description"] = 1811 "Management Network Interface"; 1812 1813 parseInterfaceData(asyncResp, iface_id, ethData, ipv4Data, 1814 ipv6Data); 1815 }); 1816 } 1817 1818 void doPatch(crow::Response &res, const crow::Request &req, 1819 const std::vector<std::string> ¶ms) override 1820 { 1821 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1822 if (params.size() != 1) 1823 { 1824 messages::internalError(asyncResp->res); 1825 return; 1826 } 1827 1828 const std::string &iface_id = params[0]; 1829 1830 std::optional<std::string> hostname; 1831 std::optional<std::string> macAddress; 1832 std::optional<std::string> ipv6DefaultGateway; 1833 std::optional<nlohmann::json> ipv4StaticAddresses; 1834 std::optional<nlohmann::json> ipv6StaticAddresses; 1835 std::optional<std::vector<std::string>> staticNameServers; 1836 std::optional<nlohmann::json> dhcpv4; 1837 std::optional<nlohmann::json> dhcpv6; 1838 std::optional<bool> interfaceEnabled; 1839 DHCPParameters v4dhcpParms; 1840 DHCPParameters v6dhcpParms; 1841 1842 if (!json_util::readJson( 1843 req, res, "HostName", hostname, "IPv4StaticAddresses", 1844 ipv4StaticAddresses, "MACAddress", macAddress, 1845 "StaticNameServers", staticNameServers, "IPv6DefaultGateway", 1846 ipv6DefaultGateway, "IPv6StaticAddresses", ipv6StaticAddresses, 1847 "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, "InterfaceEnabled", 1848 interfaceEnabled)) 1849 { 1850 return; 1851 } 1852 if (dhcpv4) 1853 { 1854 if (!json_util::readJson(*dhcpv4, res, "DHCPEnabled", 1855 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 1856 v4dhcpParms.useDNSServers, "UseNTPServers", 1857 v4dhcpParms.useNTPServers, "UseDomainName", 1858 v4dhcpParms.useUseDomainName)) 1859 { 1860 return; 1861 } 1862 } 1863 1864 if (dhcpv6) 1865 { 1866 if (!json_util::readJson(*dhcpv6, res, "OperatingMode", 1867 v6dhcpParms.dhcpv6OperatingMode, 1868 "UseDNSServers", v6dhcpParms.useDNSServers, 1869 "UseNTPServers", v6dhcpParms.useNTPServers, 1870 "UseDomainName", 1871 v6dhcpParms.useUseDomainName)) 1872 { 1873 return; 1874 } 1875 } 1876 1877 // Get single eth interface data, and call the below callback for 1878 // JSON preparation 1879 getEthernetIfaceData( 1880 iface_id, 1881 [this, asyncResp, iface_id, hostname = std::move(hostname), 1882 macAddress = std::move(macAddress), 1883 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 1884 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 1885 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 1886 staticNameServers = std::move(staticNameServers), 1887 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), 1888 v4dhcpParms = std::move(v4dhcpParms), 1889 v6dhcpParms = std::move(v6dhcpParms), 1890 interfaceEnabled = std::move(interfaceEnabled)]( 1891 const bool &success, const EthernetInterfaceData ðData, 1892 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1893 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 1894 if (!success) 1895 { 1896 // ... otherwise return error 1897 // TODO(Pawel)consider distinguish between non existing 1898 // object, and other errors 1899 messages::resourceNotFound(asyncResp->res, 1900 "Ethernet Interface", iface_id); 1901 return; 1902 } 1903 1904 if (dhcpv4 || dhcpv6) 1905 { 1906 handleDHCPPatch(iface_id, ethData, std::move(v4dhcpParms), 1907 std::move(v6dhcpParms), asyncResp); 1908 } 1909 1910 if (hostname) 1911 { 1912 handleHostnamePatch(*hostname, asyncResp); 1913 } 1914 1915 if (macAddress) 1916 { 1917 handleMACAddressPatch(iface_id, *macAddress, asyncResp); 1918 } 1919 1920 if (ipv4StaticAddresses) 1921 { 1922 // TODO(ed) for some reason the capture of ipv4Addresses 1923 // above is returning a const value, not a non-const 1924 // value. This doesn't really work for us, as we need to 1925 // be able to efficiently move out the intermedia 1926 // nlohmann::json objects. This makes a copy of the 1927 // structure, and operates on that, but could be done 1928 // more efficiently 1929 nlohmann::json ipv4Static = std::move(*ipv4StaticAddresses); 1930 handleIPv4StaticPatch(iface_id, ipv4Static, ipv4Data, 1931 asyncResp); 1932 } 1933 1934 if (staticNameServers) 1935 { 1936 handleStaticNameServersPatch(iface_id, *staticNameServers, 1937 asyncResp); 1938 } 1939 1940 if (ipv6DefaultGateway) 1941 { 1942 messages::propertyNotWritable(asyncResp->res, 1943 "IPv6DefaultGateway"); 1944 } 1945 1946 if (ipv6StaticAddresses) 1947 { 1948 nlohmann::json ipv6Static = std::move(*ipv6StaticAddresses); 1949 handleIPv6StaticAddressesPatch(iface_id, ipv6Static, 1950 ipv6Data, asyncResp); 1951 } 1952 1953 if (interfaceEnabled) 1954 { 1955 setEthernetInterfaceBoolProperty( 1956 iface_id, "NICEnabled", *interfaceEnabled, asyncResp); 1957 } 1958 }); 1959 } 1960 }; 1961 1962 /** 1963 * VlanNetworkInterface derived class for delivering VLANNetworkInterface 1964 * Schema 1965 */ 1966 class VlanNetworkInterface : public Node 1967 { 1968 public: 1969 /* 1970 * Default Constructor 1971 */ 1972 template <typename CrowApp> 1973 VlanNetworkInterface(CrowApp &app) : 1974 Node(app, 1975 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>", 1976 std::string(), std::string()) 1977 { 1978 entityPrivileges = { 1979 {boost::beast::http::verb::get, {{"Login"}}}, 1980 {boost::beast::http::verb::head, {{"Login"}}}, 1981 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1982 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1983 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1984 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1985 } 1986 1987 private: 1988 void parseInterfaceData( 1989 nlohmann::json &json_response, const std::string &parent_iface_id, 1990 const std::string &iface_id, const EthernetInterfaceData ðData, 1991 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1992 const boost::container::flat_set<IPv6AddressData> &ipv6Data) 1993 { 1994 // Fill out obvious data... 1995 json_response["Id"] = iface_id; 1996 json_response["@odata.id"] = 1997 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + parent_iface_id + 1998 "/VLANs/" + iface_id; 1999 2000 json_response["VLANEnable"] = true; 2001 if (!ethData.vlan_id.empty()) 2002 { 2003 json_response["VLANId"] = ethData.vlan_id.back(); 2004 } 2005 } 2006 2007 bool verifyNames(const std::string &parent, const std::string &iface) 2008 { 2009 if (!boost::starts_with(iface, parent + "_")) 2010 { 2011 return false; 2012 } 2013 else 2014 { 2015 return true; 2016 } 2017 } 2018 2019 /** 2020 * Functions triggers appropriate requests on DBus 2021 */ 2022 void doGet(crow::Response &res, const crow::Request &req, 2023 const std::vector<std::string> ¶ms) override 2024 { 2025 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2026 // TODO(Pawel) this shall be parameterized call (two params) to get 2027 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 2028 // Check if there is required param, truly entering this shall be 2029 // impossible. 2030 if (params.size() != 2) 2031 { 2032 messages::internalError(res); 2033 res.end(); 2034 return; 2035 } 2036 2037 const std::string &parent_iface_id = params[0]; 2038 const std::string &iface_id = params[1]; 2039 res.jsonValue["@odata.type"] = 2040 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 2041 res.jsonValue["Name"] = "VLAN Network Interface"; 2042 2043 if (!verifyNames(parent_iface_id, iface_id)) 2044 { 2045 return; 2046 } 2047 2048 // Get single eth interface data, and call the below callback for 2049 // JSON preparation 2050 getEthernetIfaceData( 2051 params[1], 2052 [this, asyncResp, parent_iface_id{std::string(params[0])}, 2053 iface_id{std::string(params[1])}]( 2054 const bool &success, const EthernetInterfaceData ðData, 2055 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 2056 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 2057 if (success && ethData.vlan_id.size() != 0) 2058 { 2059 parseInterfaceData(asyncResp->res.jsonValue, 2060 parent_iface_id, iface_id, ethData, 2061 ipv4Data, ipv6Data); 2062 } 2063 else 2064 { 2065 // ... otherwise return error 2066 // TODO(Pawel)consider distinguish between non existing 2067 // object, and other errors 2068 messages::resourceNotFound( 2069 asyncResp->res, "VLAN Network Interface", iface_id); 2070 } 2071 }); 2072 } 2073 2074 void doPatch(crow::Response &res, const crow::Request &req, 2075 const std::vector<std::string> ¶ms) override 2076 { 2077 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2078 if (params.size() != 2) 2079 { 2080 messages::internalError(asyncResp->res); 2081 return; 2082 } 2083 2084 const std::string &parentIfaceId = params[0]; 2085 const std::string &ifaceId = params[1]; 2086 2087 if (!verifyNames(parentIfaceId, ifaceId)) 2088 { 2089 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 2090 ifaceId); 2091 return; 2092 } 2093 2094 bool vlanEnable = false; 2095 uint64_t vlanId = 0; 2096 2097 if (!json_util::readJson(req, res, "VLANEnable", vlanEnable, "VLANId", 2098 vlanId)) 2099 { 2100 return; 2101 } 2102 2103 // Get single eth interface data, and call the below callback for 2104 // JSON preparation 2105 getEthernetIfaceData( 2106 params[1], 2107 [asyncResp, parentIfaceId{std::string(params[0])}, 2108 ifaceId{std::string(params[1])}, &vlanEnable, &vlanId]( 2109 const bool &success, const EthernetInterfaceData ðData, 2110 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 2111 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 2112 if (success && !ethData.vlan_id.empty()) 2113 { 2114 auto callback = 2115 [asyncResp](const boost::system::error_code ec) { 2116 if (ec) 2117 { 2118 messages::internalError(asyncResp->res); 2119 } 2120 }; 2121 2122 if (vlanEnable == true) 2123 { 2124 crow::connections::systemBus->async_method_call( 2125 std::move(callback), "xyz.openbmc_project.Network", 2126 "/xyz/openbmc_project/network/" + ifaceId, 2127 "org.freedesktop.DBus.Properties", "Set", 2128 "xyz.openbmc_project.Network.VLAN", "Id", 2129 std::variant<uint32_t>(vlanId)); 2130 } 2131 else 2132 { 2133 BMCWEB_LOG_DEBUG << "vlanEnable is false. Deleting the " 2134 "vlan interface"; 2135 crow::connections::systemBus->async_method_call( 2136 std::move(callback), "xyz.openbmc_project.Network", 2137 std::string("/xyz/openbmc_project/network/") + 2138 ifaceId, 2139 "xyz.openbmc_project.Object.Delete", "Delete"); 2140 } 2141 } 2142 else 2143 { 2144 // TODO(Pawel)consider distinguish between non existing 2145 // object, and other errors 2146 messages::resourceNotFound( 2147 asyncResp->res, "VLAN Network Interface", ifaceId); 2148 return; 2149 } 2150 }); 2151 } 2152 2153 void doDelete(crow::Response &res, const crow::Request &req, 2154 const std::vector<std::string> ¶ms) override 2155 { 2156 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2157 if (params.size() != 2) 2158 { 2159 messages::internalError(asyncResp->res); 2160 return; 2161 } 2162 2163 const std::string &parentIfaceId = params[0]; 2164 const std::string &ifaceId = params[1]; 2165 2166 if (!verifyNames(parentIfaceId, ifaceId)) 2167 { 2168 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 2169 ifaceId); 2170 return; 2171 } 2172 2173 // Get single eth interface data, and call the below callback for 2174 // JSON preparation 2175 getEthernetIfaceData( 2176 params[1], 2177 [asyncResp, parentIfaceId{std::string(params[0])}, 2178 ifaceId{std::string(params[1])}]( 2179 const bool &success, const EthernetInterfaceData ðData, 2180 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 2181 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 2182 if (success && !ethData.vlan_id.empty()) 2183 { 2184 auto callback = 2185 [asyncResp](const boost::system::error_code ec) { 2186 if (ec) 2187 { 2188 messages::internalError(asyncResp->res); 2189 } 2190 }; 2191 crow::connections::systemBus->async_method_call( 2192 std::move(callback), "xyz.openbmc_project.Network", 2193 std::string("/xyz/openbmc_project/network/") + ifaceId, 2194 "xyz.openbmc_project.Object.Delete", "Delete"); 2195 } 2196 else 2197 { 2198 // ... otherwise return error 2199 // TODO(Pawel)consider distinguish between non existing 2200 // object, and other errors 2201 messages::resourceNotFound( 2202 asyncResp->res, "VLAN Network Interface", ifaceId); 2203 } 2204 }); 2205 } 2206 }; 2207 2208 /** 2209 * VlanNetworkInterfaceCollection derived class for delivering 2210 * VLANNetworkInterface Collection Schema 2211 */ 2212 class VlanNetworkInterfaceCollection : public Node 2213 { 2214 public: 2215 template <typename CrowApp> 2216 VlanNetworkInterfaceCollection(CrowApp &app) : 2217 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/", 2218 std::string()) 2219 { 2220 entityPrivileges = { 2221 {boost::beast::http::verb::get, {{"Login"}}}, 2222 {boost::beast::http::verb::head, {{"Login"}}}, 2223 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2224 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2225 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2226 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2227 } 2228 2229 private: 2230 /** 2231 * Functions triggers appropriate requests on DBus 2232 */ 2233 void doGet(crow::Response &res, const crow::Request &req, 2234 const std::vector<std::string> ¶ms) override 2235 { 2236 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2237 if (params.size() != 1) 2238 { 2239 // This means there is a problem with the router 2240 messages::internalError(asyncResp->res); 2241 return; 2242 } 2243 2244 const std::string &rootInterfaceName = params[0]; 2245 2246 // Get eth interface list, and call the below callback for JSON 2247 // preparation 2248 getEthernetIfaceList( 2249 [asyncResp, rootInterfaceName{std::string(rootInterfaceName)}]( 2250 const bool &success, 2251 const boost::container::flat_set<std::string> &iface_list) { 2252 if (!success) 2253 { 2254 messages::internalError(asyncResp->res); 2255 return; 2256 } 2257 2258 if (iface_list.find(rootInterfaceName) == iface_list.end()) 2259 { 2260 messages::resourceNotFound(asyncResp->res, 2261 "VLanNetworkInterfaceCollection", 2262 rootInterfaceName); 2263 return; 2264 } 2265 2266 asyncResp->res.jsonValue["@odata.type"] = 2267 "#VLanNetworkInterfaceCollection." 2268 "VLanNetworkInterfaceCollection"; 2269 asyncResp->res.jsonValue["Name"] = 2270 "VLAN Network Interface Collection"; 2271 2272 nlohmann::json iface_array = nlohmann::json::array(); 2273 2274 for (const std::string &iface_item : iface_list) 2275 { 2276 if (boost::starts_with(iface_item, rootInterfaceName + "_")) 2277 { 2278 iface_array.push_back( 2279 {{"@odata.id", 2280 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2281 rootInterfaceName + "/VLANs/" + iface_item}}); 2282 } 2283 } 2284 2285 asyncResp->res.jsonValue["Members@odata.count"] = 2286 iface_array.size(); 2287 asyncResp->res.jsonValue["Members"] = std::move(iface_array); 2288 asyncResp->res.jsonValue["@odata.id"] = 2289 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2290 rootInterfaceName + "/VLANs"; 2291 }); 2292 } 2293 2294 void doPost(crow::Response &res, const crow::Request &req, 2295 const std::vector<std::string> ¶ms) override 2296 { 2297 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2298 if (params.size() != 1) 2299 { 2300 messages::internalError(asyncResp->res); 2301 return; 2302 } 2303 bool vlanEnable = false; 2304 uint32_t vlanId = 0; 2305 if (!json_util::readJson(req, res, "VLANId", vlanId, "VLANEnable", 2306 vlanEnable)) 2307 { 2308 return; 2309 } 2310 // Need both vlanId and vlanEnable to service this request 2311 if (!vlanId) 2312 { 2313 messages::propertyMissing(asyncResp->res, "VLANId"); 2314 } 2315 if (!vlanEnable) 2316 { 2317 messages::propertyMissing(asyncResp->res, "VLANEnable"); 2318 } 2319 if (static_cast<bool>(vlanId) ^ vlanEnable) 2320 { 2321 return; 2322 } 2323 2324 const std::string &rootInterfaceName = params[0]; 2325 auto callback = [asyncResp](const boost::system::error_code ec) { 2326 if (ec) 2327 { 2328 // TODO(ed) make more consistent error messages based on 2329 // phosphor-network responses 2330 messages::internalError(asyncResp->res); 2331 return; 2332 } 2333 messages::created(asyncResp->res); 2334 }; 2335 crow::connections::systemBus->async_method_call( 2336 std::move(callback), "xyz.openbmc_project.Network", 2337 "/xyz/openbmc_project/network", 2338 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2339 rootInterfaceName, vlanId); 2340 } 2341 }; 2342 } // namespace redfish 2343