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