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