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