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 sdbusplus::asio::setProperty( 733 *crow::connections::systemBus, "xyz.openbmc_project.Network", 734 "/xyz/openbmc_project/network/" + ifaceId, 735 "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway", 736 gateway, [asyncResp](const boost::system::error_code& ec) { 737 if (ec) 738 { 739 messages::internalError(asyncResp->res); 740 return; 741 } 742 asyncResp->res.result(boost::beast::http::status::no_content); 743 }); 744 } 745 /** 746 * @brief Creates a static IPv4 entry 747 * 748 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 749 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 750 * @param[in] gateway IPv4 address of this interfaces gateway 751 * @param[in] address IPv4 address to assign to this interface 752 * @param[io] asyncResp Response object that will be returned to client 753 * 754 * @return None 755 */ 756 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength, 757 const std::string& gateway, const std::string& address, 758 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 759 { 760 auto createIpHandler = [asyncResp, ifaceId, 761 gateway](const boost::system::error_code& ec) { 762 if (ec) 763 { 764 messages::internalError(asyncResp->res); 765 return; 766 } 767 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 768 }; 769 770 crow::connections::systemBus->async_method_call( 771 std::move(createIpHandler), "xyz.openbmc_project.Network", 772 "/xyz/openbmc_project/network/" + ifaceId, 773 "xyz.openbmc_project.Network.IP.Create", "IP", 774 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, 775 gateway); 776 } 777 778 /** 779 * @brief Deletes the IPv6 entry for this interface and creates a replacement 780 * static IPv6 entry 781 * 782 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 783 * @param[in] id The unique hash entry identifying the DBus entry 784 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 785 * @param[in] address IPv6 address to assign to this interface 786 * @param[io] asyncResp Response object that will be returned to client 787 * 788 * @return None 789 */ 790 791 enum class IpVersion 792 { 793 IpV4, 794 IpV6 795 }; 796 797 inline void deleteAndCreateIPAddress( 798 IpVersion version, const std::string& ifaceId, const std::string& id, 799 uint8_t prefixLength, const std::string& address, 800 const std::string& gateway, 801 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 802 { 803 crow::connections::systemBus->async_method_call( 804 [asyncResp, version, ifaceId, address, prefixLength, 805 gateway](const boost::system::error_code& ec) { 806 if (ec) 807 { 808 messages::internalError(asyncResp->res); 809 } 810 std::string protocol = "xyz.openbmc_project.Network.IP.Protocol."; 811 protocol += version == IpVersion::IpV4 ? "IPv4" : "IPv6"; 812 crow::connections::systemBus->async_method_call( 813 [asyncResp](const boost::system::error_code& ec2) { 814 if (ec2) 815 { 816 messages::internalError(asyncResp->res); 817 } 818 }, 819 "xyz.openbmc_project.Network", 820 "/xyz/openbmc_project/network/" + ifaceId, 821 "xyz.openbmc_project.Network.IP.Create", "IP", protocol, address, 822 prefixLength, gateway); 823 }, 824 "xyz.openbmc_project.Network", 825 "/xyz/openbmc_project/network/" + ifaceId + id, 826 "xyz.openbmc_project.Object.Delete", "Delete"); 827 } 828 829 inline bool extractIPv6DefaultGatewayData( 830 const std::string& ethifaceId, 831 const dbus::utility::ManagedObjectType& dbusData, 832 std::vector<StaticGatewayData>& staticGatewayConfig) 833 { 834 std::string staticGatewayPathStart("/xyz/openbmc_project/network/"); 835 staticGatewayPathStart += ethifaceId; 836 837 for (const auto& objpath : dbusData) 838 { 839 if (!std::string_view(objpath.first.str) 840 .starts_with(staticGatewayPathStart)) 841 { 842 continue; 843 } 844 for (const auto& interface : objpath.second) 845 { 846 if (interface.first != "xyz.openbmc_project.Network.StaticGateway") 847 { 848 continue; 849 } 850 StaticGatewayData& staticGateway = 851 staticGatewayConfig.emplace_back(); 852 staticGateway.id = objpath.first.filename(); 853 854 bool success = sdbusplus::unpackPropertiesNoThrow( 855 redfish::dbus_utils::UnpackErrorPrinter(), interface.second, 856 "Gateway", staticGateway.gateway, "PrefixLength", 857 staticGateway.prefixLength, "ProtocolType", 858 staticGateway.protocol); 859 if (!success) 860 { 861 return false; 862 } 863 } 864 } 865 return true; 866 } 867 868 /** 869 * @brief Creates IPv6 with given data 870 * 871 * @param[in] ifaceId Id of interface whose IP should be added 872 * @param[in] prefixLength Prefix length that needs to be added 873 * @param[in] address IP address that needs to be added 874 * @param[io] asyncResp Response object that will be returned to client 875 * 876 * @return None 877 */ 878 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength, 879 const std::string& address, 880 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 881 { 882 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 883 path /= ifaceId; 884 885 auto createIpHandler = [asyncResp, 886 address](const boost::system::error_code& ec) { 887 if (ec) 888 { 889 if (ec == boost::system::errc::io_error) 890 { 891 messages::propertyValueFormatError(asyncResp->res, address, 892 "Address"); 893 } 894 else 895 { 896 messages::internalError(asyncResp->res); 897 } 898 } 899 }; 900 // Passing null for gateway, as per redfish spec IPv6StaticAddresses 901 // object does not have associated gateway property 902 crow::connections::systemBus->async_method_call( 903 std::move(createIpHandler), "xyz.openbmc_project.Network", path, 904 "xyz.openbmc_project.Network.IP.Create", "IP", 905 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 906 ""); 907 } 908 909 /** 910 * @brief Deletes given IPv6 Static Gateway 911 * 912 * @param[in] ifaceId Id of interface whose IP should be deleted 913 * @param[in] ipHash DBus Hash id of IP that should be deleted 914 * @param[io] asyncResp Response object that will be returned to client 915 * 916 * @return None 917 */ 918 inline void 919 deleteIPv6Gateway(std::string_view gatewayId, 920 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 921 { 922 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 923 path /= gatewayId; 924 crow::connections::systemBus->async_method_call( 925 [asyncResp](const boost::system::error_code& ec) { 926 if (ec) 927 { 928 messages::internalError(asyncResp->res); 929 } 930 }, 931 "xyz.openbmc_project.Network", path, 932 "xyz.openbmc_project.Object.Delete", "Delete"); 933 } 934 935 /** 936 * @brief Creates IPv6 static default gateway with given data 937 * 938 * @param[in] ifaceId Id of interface whose IP should be added 939 * @param[in] prefixLength Prefix length that needs to be added 940 * @param[in] gateway Gateway address that needs to be added 941 * @param[io] asyncResp Response object that will be returned to client 942 * 943 * @return None 944 */ 945 inline void createIPv6DefaultGateway( 946 std::string_view ifaceId, size_t prefixLength, std::string_view gateway, 947 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 948 { 949 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 950 path /= ifaceId; 951 auto createIpHandler = [asyncResp](const boost::system::error_code& ec) { 952 if (ec) 953 { 954 messages::internalError(asyncResp->res); 955 } 956 }; 957 crow::connections::systemBus->async_method_call( 958 std::move(createIpHandler), "xyz.openbmc_project.Network", path, 959 "xyz.openbmc_project.Network.StaticGateway.Create", "StaticGateway", 960 gateway, prefixLength, "xyz.openbmc_project.Network.IP.Protocol.IPv6"); 961 } 962 963 /** 964 * @brief Deletes the IPv6 default gateway entry for this interface and 965 * creates a replacement IPv6 default gateway entry 966 * 967 * @param[in] ifaceId Id of interface upon which to create the IPv6 968 * entry 969 * @param[in] gateway IPv6 gateway to assign to this interface 970 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 971 * @param[io] asyncResp Response object that will be returned to client 972 * 973 * @return None 974 */ 975 inline void deleteAndCreateIPv6DefaultGateway( 976 std::string_view ifaceId, std::string_view gatewayId, 977 std::string_view gateway, size_t prefixLength, 978 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 979 { 980 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 981 path /= gatewayId; 982 crow::connections::systemBus->async_method_call( 983 [asyncResp, ifaceId, gateway, 984 prefixLength](const boost::system::error_code& ec) { 985 if (ec) 986 { 987 messages::internalError(asyncResp->res); 988 return; 989 } 990 createIPv6DefaultGateway(ifaceId, prefixLength, gateway, asyncResp); 991 }, 992 "xyz.openbmc_project.Network", path, 993 "xyz.openbmc_project.Object.Delete", "Delete"); 994 } 995 996 /** 997 * @brief Sets IPv6 default gateway with given data 998 * 999 * @param[in] ifaceId Id of interface whose gateway should be added 1000 * @param[in] input Contains address that needs to be added 1001 * @param[in] staticGatewayData Current static gateways in the system 1002 * @param[io] asyncResp Response object that will be returned to client 1003 * 1004 * @return None 1005 */ 1006 1007 inline void handleIPv6DefaultGateway( 1008 const std::string& ifaceId, const nlohmann::json::array_t& input, 1009 const std::vector<StaticGatewayData>& staticGatewayData, 1010 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1011 { 1012 size_t entryIdx = 1; 1013 std::vector<StaticGatewayData>::const_iterator staticGatewayEntry = 1014 staticGatewayData.begin(); 1015 1016 for (const nlohmann::json& thisJson : input) 1017 { 1018 // find the next gateway entry 1019 while (staticGatewayEntry != staticGatewayData.end()) 1020 { 1021 if (staticGatewayEntry->protocol == 1022 "xyz.openbmc_project.Network.IP.Protocol.IPv6") 1023 { 1024 break; 1025 } 1026 staticGatewayEntry++; 1027 } 1028 std::string pathString = "IPv6StaticDefaultGateways/" + 1029 std::to_string(entryIdx); 1030 if (thisJson.is_null()) 1031 { 1032 if (staticGatewayEntry == staticGatewayData.end()) 1033 { 1034 messages::resourceCannotBeDeleted(asyncResp->res); 1035 return; 1036 } 1037 deleteIPv6Gateway(staticGatewayEntry->id, asyncResp); 1038 return; 1039 } 1040 if (thisJson.is_object() && thisJson.empty()) 1041 { 1042 // Do nothing, but make sure the entry exists. 1043 if (staticGatewayEntry == staticGatewayData.end()) 1044 { 1045 messages::propertyValueFormatError(asyncResp->res, thisJson, 1046 pathString); 1047 return; 1048 } 1049 } 1050 std::optional<std::string> address; 1051 std::optional<size_t> prefixLength; 1052 1053 nlohmann::json thisJsonCopy = thisJson; 1054 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1055 address, "PrefixLength", prefixLength)) 1056 { 1057 return; 1058 } 1059 const std::string* addr = nullptr; 1060 size_t prefix = 0; 1061 if (address) 1062 { 1063 addr = &(*address); 1064 } 1065 else if (staticGatewayEntry != staticGatewayData.end()) 1066 { 1067 addr = &(staticGatewayEntry->gateway); 1068 } 1069 else 1070 { 1071 messages::propertyMissing(asyncResp->res, pathString + "/Address"); 1072 return; 1073 } 1074 if (prefixLength) 1075 { 1076 prefix = *prefixLength; 1077 } 1078 else if (staticGatewayEntry != staticGatewayData.end()) 1079 { 1080 prefix = staticGatewayEntry->prefixLength; 1081 } 1082 else 1083 { 1084 messages::propertyMissing(asyncResp->res, 1085 pathString + "/PrefixLength"); 1086 return; 1087 } 1088 if (staticGatewayEntry != staticGatewayData.end()) 1089 { 1090 deleteAndCreateIPv6DefaultGateway(ifaceId, staticGatewayEntry->id, 1091 *addr, prefix, asyncResp); 1092 staticGatewayEntry++; 1093 } 1094 else 1095 { 1096 createIPv6DefaultGateway(ifaceId, prefix, *addr, asyncResp); 1097 } 1098 entryIdx++; 1099 } 1100 } 1101 1102 /** 1103 * Function that retrieves all properties for given Ethernet Interface 1104 * Object 1105 * from EntityManager Network Manager 1106 * @param ethiface_id a eth interface id to query on DBus 1107 * @param callback a function that shall be called to convert Dbus output 1108 * into JSON 1109 */ 1110 template <typename CallbackFunc> 1111 void getEthernetIfaceData(const std::string& ethifaceId, 1112 CallbackFunc&& callback) 1113 { 1114 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1115 dbus::utility::getManagedObjects( 1116 "xyz.openbmc_project.Network", path, 1117 [ethifaceId{std::string{ethifaceId}}, 1118 callback{std::forward<CallbackFunc>(callback)}]( 1119 const boost::system::error_code& ec, 1120 const dbus::utility::ManagedObjectType& resp) { 1121 EthernetInterfaceData ethData{}; 1122 std::vector<IPv4AddressData> ipv4Data; 1123 std::vector<IPv6AddressData> ipv6Data; 1124 std::vector<StaticGatewayData> ipv6GatewayData; 1125 1126 if (ec) 1127 { 1128 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1129 return; 1130 } 1131 1132 bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData); 1133 if (!found) 1134 { 1135 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1136 return; 1137 } 1138 1139 extractIPData(ethifaceId, resp, ipv4Data); 1140 // Fix global GW 1141 for (IPv4AddressData& ipv4 : ipv4Data) 1142 { 1143 if (((ipv4.linktype == LinkType::Global) && 1144 (ipv4.gateway == "0.0.0.0")) || 1145 (ipv4.origin == "DHCP") || (ipv4.origin == "Static")) 1146 { 1147 ipv4.gateway = ethData.defaultGateway; 1148 } 1149 } 1150 1151 extractIPV6Data(ethifaceId, resp, ipv6Data); 1152 if (!extractIPv6DefaultGatewayData(ethifaceId, resp, ipv6GatewayData)) 1153 { 1154 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1155 } 1156 // Finally make a callback with useful data 1157 callback(true, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1158 }); 1159 } 1160 1161 /** 1162 * Function that retrieves all Ethernet Interfaces available through Network 1163 * Manager 1164 * @param callback a function that shall be called to convert Dbus output 1165 * into JSON. 1166 */ 1167 template <typename CallbackFunc> 1168 void getEthernetIfaceList(CallbackFunc&& callback) 1169 { 1170 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1171 dbus::utility::getManagedObjects( 1172 "xyz.openbmc_project.Network", path, 1173 [callback{std::forward<CallbackFunc>(callback)}]( 1174 const boost::system::error_code& ec, 1175 const dbus::utility::ManagedObjectType& resp) { 1176 // Callback requires vector<string> to retrieve all available 1177 // ethernet interfaces 1178 std::vector<std::string> ifaceList; 1179 ifaceList.reserve(resp.size()); 1180 if (ec) 1181 { 1182 callback(false, ifaceList); 1183 return; 1184 } 1185 1186 // Iterate over all retrieved ObjectPaths. 1187 for (const auto& objpath : resp) 1188 { 1189 // And all interfaces available for certain ObjectPath. 1190 for (const auto& interface : objpath.second) 1191 { 1192 // If interface is 1193 // xyz.openbmc_project.Network.EthernetInterface, this is 1194 // what we're looking for. 1195 if (interface.first == 1196 "xyz.openbmc_project.Network.EthernetInterface") 1197 { 1198 std::string ifaceId = objpath.first.filename(); 1199 if (ifaceId.empty()) 1200 { 1201 continue; 1202 } 1203 // and put it into output vector. 1204 ifaceList.emplace_back(ifaceId); 1205 } 1206 } 1207 } 1208 1209 std::ranges::sort(ifaceList, AlphanumLess<std::string>()); 1210 1211 // Finally make a callback with useful data 1212 callback(true, ifaceList); 1213 }); 1214 } 1215 1216 inline void 1217 handleHostnamePatch(const std::string& hostname, 1218 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1219 { 1220 // SHOULD handle host names of up to 255 characters(RFC 1123) 1221 if (hostname.length() > 255) 1222 { 1223 messages::propertyValueFormatError(asyncResp->res, hostname, 1224 "HostName"); 1225 return; 1226 } 1227 sdbusplus::asio::setProperty( 1228 *crow::connections::systemBus, "xyz.openbmc_project.Network", 1229 "/xyz/openbmc_project/network/config", 1230 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", hostname, 1231 [asyncResp](const boost::system::error_code& ec) { 1232 if (ec) 1233 { 1234 messages::internalError(asyncResp->res); 1235 } 1236 }); 1237 } 1238 1239 inline void 1240 handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize, 1241 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1242 { 1243 sdbusplus::message::object_path objPath = "/xyz/openbmc_project/network/" + 1244 ifaceId; 1245 sdbusplus::asio::setProperty( 1246 *crow::connections::systemBus, "xyz.openbmc_project.Network", objPath, 1247 "xyz.openbmc_project.Network.EthernetInterface", "MTU", mtuSize, 1248 [asyncResp](const boost::system::error_code& ec) { 1249 if (ec) 1250 { 1251 messages::internalError(asyncResp->res); 1252 } 1253 }); 1254 } 1255 1256 inline void 1257 handleDomainnamePatch(const std::string& ifaceId, 1258 const std::string& domainname, 1259 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1260 { 1261 std::vector<std::string> vectorDomainname = {domainname}; 1262 sdbusplus::asio::setProperty( 1263 *crow::connections::systemBus, "xyz.openbmc_project.Network", 1264 "/xyz/openbmc_project/network/" + ifaceId, 1265 "xyz.openbmc_project.Network.EthernetInterface", "DomainName", 1266 vectorDomainname, [asyncResp](const boost::system::error_code& ec) { 1267 if (ec) 1268 { 1269 messages::internalError(asyncResp->res); 1270 } 1271 }); 1272 } 1273 1274 inline bool isHostnameValid(const std::string& hostname) 1275 { 1276 // A valid host name can never have the dotted-decimal form (RFC 1123) 1277 if (std::ranges::all_of(hostname, ::isdigit)) 1278 { 1279 return false; 1280 } 1281 // Each label(hostname/subdomains) within a valid FQDN 1282 // MUST handle host names of up to 63 characters (RFC 1123) 1283 // labels cannot start or end with hyphens (RFC 952) 1284 // labels can start with numbers (RFC 1123) 1285 const static std::regex pattern( 1286 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 1287 1288 return std::regex_match(hostname, pattern); 1289 } 1290 1291 inline bool isDomainnameValid(const std::string& domainname) 1292 { 1293 // Can have multiple subdomains 1294 // Top Level Domain's min length is 2 character 1295 const static std::regex pattern( 1296 "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$"); 1297 1298 return std::regex_match(domainname, pattern); 1299 } 1300 1301 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn, 1302 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1303 { 1304 // Total length of FQDN must not exceed 255 characters(RFC 1035) 1305 if (fqdn.length() > 255) 1306 { 1307 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1308 return; 1309 } 1310 1311 size_t pos = fqdn.find('.'); 1312 if (pos == std::string::npos) 1313 { 1314 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1315 return; 1316 } 1317 1318 std::string hostname; 1319 std::string domainname; 1320 domainname = (fqdn).substr(pos + 1); 1321 hostname = (fqdn).substr(0, pos); 1322 1323 if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) 1324 { 1325 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1326 return; 1327 } 1328 1329 handleHostnamePatch(hostname, asyncResp); 1330 handleDomainnamePatch(ifaceId, domainname, asyncResp); 1331 } 1332 1333 inline void 1334 handleMACAddressPatch(const std::string& ifaceId, 1335 const std::string& macAddress, 1336 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1337 { 1338 static constexpr std::string_view dbusNotAllowedError = 1339 "xyz.openbmc_project.Common.Error.NotAllowed"; 1340 1341 sdbusplus::asio::setProperty( 1342 *crow::connections::systemBus, "xyz.openbmc_project.Network", 1343 "/xyz/openbmc_project/network/" + ifaceId, 1344 "xyz.openbmc_project.Network.MACAddress", "MACAddress", macAddress, 1345 [asyncResp](const boost::system::error_code& ec, 1346 const sdbusplus::message_t& msg) { 1347 if (ec) 1348 { 1349 const sd_bus_error* err = msg.get_error(); 1350 if (err == nullptr) 1351 { 1352 messages::internalError(asyncResp->res); 1353 return; 1354 } 1355 if (err->name == dbusNotAllowedError) 1356 { 1357 messages::propertyNotWritable(asyncResp->res, "MACAddress"); 1358 return; 1359 } 1360 messages::internalError(asyncResp->res); 1361 return; 1362 } 1363 }); 1364 } 1365 1366 inline void setDHCPEnabled(const std::string& ifaceId, 1367 const std::string& propertyName, const bool v4Value, 1368 const bool v6Value, 1369 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1370 { 1371 const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value); 1372 sdbusplus::asio::setProperty( 1373 *crow::connections::systemBus, "xyz.openbmc_project.Network", 1374 "/xyz/openbmc_project/network/" + ifaceId, 1375 "xyz.openbmc_project.Network.EthernetInterface", propertyName, dhcp, 1376 [asyncResp](const boost::system::error_code& ec) { 1377 if (ec) 1378 { 1379 BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec); 1380 messages::internalError(asyncResp->res); 1381 return; 1382 } 1383 messages::success(asyncResp->res); 1384 }); 1385 } 1386 1387 inline void setEthernetInterfaceBoolProperty( 1388 const std::string& ifaceId, const std::string& propertyName, 1389 const bool& value, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1390 { 1391 sdbusplus::asio::setProperty( 1392 *crow::connections::systemBus, "xyz.openbmc_project.Network", 1393 "/xyz/openbmc_project/network/" + ifaceId, 1394 "xyz.openbmc_project.Network.EthernetInterface", propertyName, value, 1395 [asyncResp](const boost::system::error_code& ec) { 1396 if (ec) 1397 { 1398 BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec); 1399 messages::internalError(asyncResp->res); 1400 return; 1401 } 1402 }); 1403 } 1404 1405 enum class NetworkType 1406 { 1407 dhcp4, 1408 dhcp6 1409 }; 1410 1411 inline void setDHCPConfig(const std::string& propertyName, const bool& value, 1412 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1413 const std::string& ethifaceId, NetworkType type) 1414 { 1415 BMCWEB_LOG_DEBUG("{} = {}", propertyName, value); 1416 sdbusplus::message::object_path path("/xyz/openbmc_project/network/"); 1417 path /= ethifaceId; 1418 1419 if (type == NetworkType::dhcp4) 1420 { 1421 path /= "dhcp4"; 1422 } 1423 else 1424 { 1425 path /= "dhcp6"; 1426 } 1427 1428 sdbusplus::asio::setProperty( 1429 *crow::connections::systemBus, "xyz.openbmc_project.Network", path, 1430 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, value, 1431 [asyncResp](const boost::system::error_code& ec) { 1432 if (ec) 1433 { 1434 BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec); 1435 messages::internalError(asyncResp->res); 1436 return; 1437 } 1438 }); 1439 } 1440 1441 inline void handleSLAACAutoConfigPatch( 1442 const std::string& ifaceId, bool ipv6AutoConfigEnabled, 1443 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1444 { 1445 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1446 path /= ifaceId; 1447 sdbusplus::asio::setProperty( 1448 *crow::connections::systemBus, "xyz.openbmc_project.Network", path, 1449 "xyz.openbmc_project.Network.EthernetInterface", "IPv6AcceptRA", 1450 ipv6AutoConfigEnabled, 1451 [asyncResp](const boost::system::error_code& ec) { 1452 if (ec) 1453 { 1454 BMCWEB_LOG_ERROR("D-Bus responses error: {}", ec); 1455 messages::internalError(asyncResp->res); 1456 return; 1457 } 1458 messages::success(asyncResp->res); 1459 }); 1460 } 1461 1462 inline void handleDHCPPatch(const std::string& ifaceId, 1463 const EthernetInterfaceData& ethData, 1464 const DHCPParameters& v4dhcpParms, 1465 const DHCPParameters& v6dhcpParms, 1466 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1467 { 1468 bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1469 bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false); 1470 1471 bool nextv4DHCPState = 1472 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1473 1474 bool nextv6DHCPState{}; 1475 if (v6dhcpParms.dhcpv6OperatingMode) 1476 { 1477 if ((*v6dhcpParms.dhcpv6OperatingMode != "Enabled") && 1478 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1479 { 1480 messages::propertyValueFormatError(asyncResp->res, 1481 *v6dhcpParms.dhcpv6OperatingMode, 1482 "OperatingMode"); 1483 return; 1484 } 1485 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Enabled"); 1486 } 1487 else 1488 { 1489 nextv6DHCPState = ipv6Active; 1490 } 1491 1492 bool nextDNSv4 = ethData.dnsv4Enabled; 1493 bool nextDNSv6 = ethData.dnsv6Enabled; 1494 if (v4dhcpParms.useDnsServers) 1495 { 1496 nextDNSv4 = *v4dhcpParms.useDnsServers; 1497 } 1498 if (v6dhcpParms.useDnsServers) 1499 { 1500 nextDNSv6 = *v6dhcpParms.useDnsServers; 1501 } 1502 1503 bool nextNTPv4 = ethData.ntpv4Enabled; 1504 bool nextNTPv6 = ethData.ntpv6Enabled; 1505 if (v4dhcpParms.useNtpServers) 1506 { 1507 nextNTPv4 = *v4dhcpParms.useNtpServers; 1508 } 1509 if (v6dhcpParms.useNtpServers) 1510 { 1511 nextNTPv6 = *v6dhcpParms.useNtpServers; 1512 } 1513 1514 bool nextUsev4Domain = ethData.domainv4Enabled; 1515 bool nextUsev6Domain = ethData.domainv6Enabled; 1516 if (v4dhcpParms.useDomainName) 1517 { 1518 nextUsev4Domain = *v4dhcpParms.useDomainName; 1519 } 1520 if (v6dhcpParms.useDomainName) 1521 { 1522 nextUsev6Domain = *v6dhcpParms.useDomainName; 1523 } 1524 1525 BMCWEB_LOG_DEBUG("set DHCPEnabled..."); 1526 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1527 asyncResp); 1528 BMCWEB_LOG_DEBUG("set DNSEnabled..."); 1529 setDHCPConfig("DNSEnabled", nextDNSv4, asyncResp, ifaceId, 1530 NetworkType::dhcp4); 1531 BMCWEB_LOG_DEBUG("set NTPEnabled..."); 1532 setDHCPConfig("NTPEnabled", nextNTPv4, asyncResp, ifaceId, 1533 NetworkType::dhcp4); 1534 BMCWEB_LOG_DEBUG("set DomainEnabled..."); 1535 setDHCPConfig("DomainEnabled", nextUsev4Domain, asyncResp, ifaceId, 1536 NetworkType::dhcp4); 1537 BMCWEB_LOG_DEBUG("set DNSEnabled for dhcp6..."); 1538 setDHCPConfig("DNSEnabled", nextDNSv6, asyncResp, ifaceId, 1539 NetworkType::dhcp6); 1540 BMCWEB_LOG_DEBUG("set NTPEnabled for dhcp6..."); 1541 setDHCPConfig("NTPEnabled", nextNTPv6, asyncResp, ifaceId, 1542 NetworkType::dhcp6); 1543 BMCWEB_LOG_DEBUG("set DomainEnabled for dhcp6..."); 1544 setDHCPConfig("DomainEnabled", nextUsev6Domain, asyncResp, ifaceId, 1545 NetworkType::dhcp6); 1546 } 1547 1548 inline std::vector<IPv4AddressData>::const_iterator getNextStaticIpEntry( 1549 const std::vector<IPv4AddressData>::const_iterator& head, 1550 const std::vector<IPv4AddressData>::const_iterator& end) 1551 { 1552 return std::find_if(head, end, [](const IPv4AddressData& value) { 1553 return value.origin == "Static"; 1554 }); 1555 } 1556 1557 inline std::vector<IPv6AddressData>::const_iterator getNextStaticIpEntry( 1558 const std::vector<IPv6AddressData>::const_iterator& head, 1559 const std::vector<IPv6AddressData>::const_iterator& end) 1560 { 1561 return std::find_if(head, end, [](const IPv6AddressData& value) { 1562 return value.origin == "Static"; 1563 }); 1564 } 1565 1566 inline void 1567 handleIPv4StaticPatch(const std::string& ifaceId, 1568 nlohmann::json::array_t& input, 1569 const std::vector<IPv4AddressData>& ipv4Data, 1570 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1571 { 1572 if (input.empty()) 1573 { 1574 messages::propertyValueTypeError(asyncResp->res, input, 1575 "IPv4StaticAddresses"); 1576 return; 1577 } 1578 1579 unsigned entryIdx = 1; 1580 // Find the first static IP address currently active on the NIC and 1581 // match it to the first JSON element in the IPv4StaticAddresses array. 1582 // Match each subsequent JSON element to the next static IP programmed 1583 // into the NIC. 1584 std::vector<IPv4AddressData>::const_iterator nicIpEntry = 1585 getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1586 1587 for (nlohmann::json& thisJson : input) 1588 { 1589 std::string pathString = "IPv4StaticAddresses/" + 1590 std::to_string(entryIdx); 1591 1592 if (!thisJson.is_null() && !thisJson.empty()) 1593 { 1594 std::optional<std::string> address; 1595 std::optional<std::string> subnetMask; 1596 std::optional<std::string> gateway; 1597 1598 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1599 address, "SubnetMask", subnetMask, 1600 "Gateway", gateway)) 1601 { 1602 messages::propertyValueFormatError(asyncResp->res, thisJson, 1603 pathString); 1604 return; 1605 } 1606 1607 // Find the address/subnet/gateway values. Any values that are 1608 // not explicitly provided are assumed to be unmodified from the 1609 // current state of the interface. Merge existing state into the 1610 // current request. 1611 if (address) 1612 { 1613 if (!ip_util::ipv4VerifyIpAndGetBitcount(*address)) 1614 { 1615 messages::propertyValueFormatError(asyncResp->res, *address, 1616 pathString + "/Address"); 1617 return; 1618 } 1619 } 1620 else if (nicIpEntry != ipv4Data.cend()) 1621 { 1622 address = (nicIpEntry->address); 1623 } 1624 else 1625 { 1626 messages::propertyMissing(asyncResp->res, 1627 pathString + "/Address"); 1628 return; 1629 } 1630 1631 uint8_t prefixLength = 0; 1632 if (subnetMask) 1633 { 1634 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask, 1635 &prefixLength)) 1636 { 1637 messages::propertyValueFormatError( 1638 asyncResp->res, *subnetMask, 1639 pathString + "/SubnetMask"); 1640 return; 1641 } 1642 } 1643 else if (nicIpEntry != ipv4Data.cend()) 1644 { 1645 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, 1646 &prefixLength)) 1647 { 1648 messages::propertyValueFormatError( 1649 asyncResp->res, nicIpEntry->netmask, 1650 pathString + "/SubnetMask"); 1651 return; 1652 } 1653 } 1654 else 1655 { 1656 messages::propertyMissing(asyncResp->res, 1657 pathString + "/SubnetMask"); 1658 return; 1659 } 1660 1661 if (gateway) 1662 { 1663 if (!ip_util::ipv4VerifyIpAndGetBitcount(*gateway)) 1664 { 1665 messages::propertyValueFormatError(asyncResp->res, *gateway, 1666 pathString + "/Gateway"); 1667 return; 1668 } 1669 } 1670 else if (nicIpEntry != ipv4Data.cend()) 1671 { 1672 gateway = nicIpEntry->gateway; 1673 } 1674 else 1675 { 1676 messages::propertyMissing(asyncResp->res, 1677 pathString + "/Gateway"); 1678 return; 1679 } 1680 1681 if (nicIpEntry != ipv4Data.cend()) 1682 { 1683 deleteAndCreateIPAddress(IpVersion::IpV4, ifaceId, 1684 nicIpEntry->id, prefixLength, *address, 1685 *gateway, asyncResp); 1686 nicIpEntry = getNextStaticIpEntry(++nicIpEntry, 1687 ipv4Data.cend()); 1688 } 1689 else 1690 { 1691 createIPv4(ifaceId, prefixLength, *gateway, *address, 1692 asyncResp); 1693 } 1694 entryIdx++; 1695 } 1696 else 1697 { 1698 if (nicIpEntry == ipv4Data.cend()) 1699 { 1700 // Requesting a DELETE/DO NOT MODIFY action for an item 1701 // that isn't present on the eth(n) interface. Input JSON is 1702 // in error, so bail out. 1703 if (thisJson.is_null()) 1704 { 1705 messages::resourceCannotBeDeleted(asyncResp->res); 1706 return; 1707 } 1708 messages::propertyValueFormatError(asyncResp->res, thisJson, 1709 pathString); 1710 return; 1711 } 1712 1713 if (thisJson.is_null()) 1714 { 1715 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp); 1716 } 1717 if (nicIpEntry != ipv4Data.cend()) 1718 { 1719 nicIpEntry = getNextStaticIpEntry(++nicIpEntry, 1720 ipv4Data.cend()); 1721 } 1722 entryIdx++; 1723 } 1724 } 1725 } 1726 1727 inline void handleStaticNameServersPatch( 1728 const std::string& ifaceId, 1729 const std::vector<std::string>& updatedStaticNameServers, 1730 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1731 { 1732 sdbusplus::asio::setProperty( 1733 *crow::connections::systemBus, "xyz.openbmc_project.Network", 1734 "/xyz/openbmc_project/network/" + ifaceId, 1735 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", 1736 updatedStaticNameServers, 1737 [asyncResp](const boost::system::error_code& ec) { 1738 if (ec) 1739 { 1740 messages::internalError(asyncResp->res); 1741 return; 1742 } 1743 }); 1744 } 1745 1746 inline void handleIPv6StaticAddressesPatch( 1747 const std::string& ifaceId, const nlohmann::json::array_t& input, 1748 const std::vector<IPv6AddressData>& ipv6Data, 1749 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1750 { 1751 if (input.empty()) 1752 { 1753 messages::propertyValueTypeError(asyncResp->res, input, 1754 "IPv6StaticAddresses"); 1755 return; 1756 } 1757 size_t entryIdx = 1; 1758 std::vector<IPv6AddressData>::const_iterator nicIpEntry = 1759 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1760 for (const nlohmann::json& thisJson : input) 1761 { 1762 std::string pathString = "IPv6StaticAddresses/" + 1763 std::to_string(entryIdx); 1764 1765 if (!thisJson.is_null() && !thisJson.empty()) 1766 { 1767 std::optional<std::string> address; 1768 std::optional<uint8_t> prefixLength; 1769 nlohmann::json thisJsonCopy = thisJson; 1770 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1771 address, "PrefixLength", prefixLength)) 1772 { 1773 messages::propertyValueFormatError(asyncResp->res, thisJson, 1774 pathString); 1775 return; 1776 } 1777 1778 const std::string* addr = nullptr; 1779 uint8_t prefix = 0; 1780 1781 // Find the address and prefixLength values. Any values that are 1782 // not explicitly provided are assumed to be unmodified from the 1783 // current state of the interface. Merge existing state into the 1784 // current request. 1785 if (address) 1786 { 1787 addr = &(*address); 1788 } 1789 else if (nicIpEntry != ipv6Data.end()) 1790 { 1791 addr = &(nicIpEntry->address); 1792 } 1793 else 1794 { 1795 messages::propertyMissing(asyncResp->res, 1796 pathString + "/Address"); 1797 return; 1798 } 1799 1800 if (prefixLength) 1801 { 1802 prefix = *prefixLength; 1803 } 1804 else if (nicIpEntry != ipv6Data.end()) 1805 { 1806 prefix = nicIpEntry->prefixLength; 1807 } 1808 else 1809 { 1810 messages::propertyMissing(asyncResp->res, 1811 pathString + "/PrefixLength"); 1812 return; 1813 } 1814 1815 if (nicIpEntry != ipv6Data.end()) 1816 { 1817 deleteAndCreateIPAddress(IpVersion::IpV6, ifaceId, 1818 nicIpEntry->id, prefix, *addr, "", 1819 asyncResp); 1820 nicIpEntry = getNextStaticIpEntry(++nicIpEntry, 1821 ipv6Data.cend()); 1822 } 1823 else 1824 { 1825 createIPv6(ifaceId, *prefixLength, *addr, asyncResp); 1826 } 1827 entryIdx++; 1828 } 1829 else 1830 { 1831 if (nicIpEntry == ipv6Data.end()) 1832 { 1833 // Requesting a DELETE/DO NOT MODIFY action for an item 1834 // that isn't present on the eth(n) interface. Input JSON is 1835 // in error, so bail out. 1836 if (thisJson.is_null()) 1837 { 1838 messages::resourceCannotBeDeleted(asyncResp->res); 1839 return; 1840 } 1841 messages::propertyValueFormatError(asyncResp->res, thisJson, 1842 pathString); 1843 return; 1844 } 1845 1846 if (thisJson.is_null()) 1847 { 1848 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp); 1849 } 1850 if (nicIpEntry != ipv6Data.cend()) 1851 { 1852 nicIpEntry = getNextStaticIpEntry(++nicIpEntry, 1853 ipv6Data.cend()); 1854 } 1855 entryIdx++; 1856 } 1857 } 1858 } 1859 1860 inline std::string extractParentInterfaceName(const std::string& ifaceId) 1861 { 1862 std::size_t pos = ifaceId.find('_'); 1863 return ifaceId.substr(0, pos); 1864 } 1865 1866 inline void 1867 parseInterfaceData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1868 const std::string& ifaceId, 1869 const EthernetInterfaceData& ethData, 1870 const std::vector<IPv4AddressData>& ipv4Data, 1871 const std::vector<IPv6AddressData>& ipv6Data, 1872 const std::vector<StaticGatewayData>& ipv6GatewayData) 1873 { 1874 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1875 jsonResponse["Id"] = ifaceId; 1876 jsonResponse["@odata.id"] = boost::urls::format( 1877 "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", ifaceId); 1878 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1879 1880 if constexpr (bmcwebEnableHealthPopulate) 1881 { 1882 constexpr std::array<std::string_view, 1> inventoryForEthernet = { 1883 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1884 auto health = std::make_shared<HealthPopulate>(asyncResp); 1885 dbus::utility::getSubTreePaths( 1886 "/", 0, inventoryForEthernet, 1887 [health](const boost::system::error_code& ec, 1888 const dbus::utility::MapperGetSubTreePathsResponse& resp) { 1889 if (ec) 1890 { 1891 return; 1892 } 1893 1894 health->inventory = resp; 1895 }); 1896 1897 health->populate(); 1898 } 1899 1900 if (ethData.nicEnabled) 1901 { 1902 jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1903 jsonResponse["Status"]["State"] = "Enabled"; 1904 } 1905 else 1906 { 1907 jsonResponse["LinkStatus"] = "NoLink"; 1908 jsonResponse["Status"]["State"] = "Disabled"; 1909 } 1910 1911 jsonResponse["SpeedMbps"] = ethData.speed; 1912 jsonResponse["MTUSize"] = ethData.mtuSize; 1913 jsonResponse["MACAddress"] = ethData.macAddress; 1914 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1915 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1916 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled; 1917 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled; 1918 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNamev4Enabled; 1919 jsonResponse["DHCPv6"]["OperatingMode"] = 1920 translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Enabled" 1921 : "Disabled"; 1922 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled; 1923 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled; 1924 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNamev6Enabled; 1925 jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] = 1926 ethData.ipv6AcceptRa; 1927 1928 if (!ethData.hostName.empty()) 1929 { 1930 jsonResponse["HostName"] = ethData.hostName; 1931 1932 // When domain name is empty then it means, that it is a network 1933 // without domain names, and the host name itself must be treated as 1934 // FQDN 1935 std::string fqdn = ethData.hostName; 1936 if (!ethData.domainnames.empty()) 1937 { 1938 fqdn += "." + ethData.domainnames[0]; 1939 } 1940 jsonResponse["FQDN"] = fqdn; 1941 } 1942 1943 if (ethData.vlanId) 1944 { 1945 jsonResponse["EthernetInterfaceType"] = "Virtual"; 1946 jsonResponse["VLAN"]["VLANEnable"] = true; 1947 jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId; 1948 jsonResponse["VLAN"]["Tagged"] = true; 1949 1950 nlohmann::json::array_t relatedInterfaces; 1951 nlohmann::json& parentInterface = relatedInterfaces.emplace_back(); 1952 parentInterface["@odata.id"] = 1953 boost::urls::format("/redfish/v1/Managers/bmc/EthernetInterfaces", 1954 extractParentInterfaceName(ifaceId)); 1955 jsonResponse["Links"]["RelatedInterfaces"] = 1956 std::move(relatedInterfaces); 1957 } 1958 else 1959 { 1960 jsonResponse["EthernetInterfaceType"] = "Physical"; 1961 } 1962 1963 jsonResponse["NameServers"] = ethData.nameServers; 1964 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1965 1966 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1967 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1968 ipv4Array = nlohmann::json::array(); 1969 ipv4StaticArray = nlohmann::json::array(); 1970 for (const auto& ipv4Config : ipv4Data) 1971 { 1972 std::string gatewayStr = ipv4Config.gateway; 1973 if (gatewayStr.empty()) 1974 { 1975 gatewayStr = "0.0.0.0"; 1976 } 1977 nlohmann::json::object_t ipv4; 1978 ipv4["AddressOrigin"] = ipv4Config.origin; 1979 ipv4["SubnetMask"] = ipv4Config.netmask; 1980 ipv4["Address"] = ipv4Config.address; 1981 ipv4["Gateway"] = gatewayStr; 1982 1983 if (ipv4Config.origin == "Static") 1984 { 1985 ipv4StaticArray.push_back(ipv4); 1986 } 1987 1988 ipv4Array.emplace_back(std::move(ipv4)); 1989 } 1990 1991 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; 1992 if (ipv6GatewayStr.empty()) 1993 { 1994 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1995 } 1996 1997 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1998 1999 nlohmann::json::array_t ipv6StaticGatewayArray; 2000 for (const auto& ipv6GatewayConfig : ipv6GatewayData) 2001 { 2002 nlohmann::json::object_t ipv6Gateway; 2003 ipv6Gateway["Address"] = ipv6GatewayConfig.gateway; 2004 ipv6Gateway["PrefixLength"] = ipv6GatewayConfig.prefixLength; 2005 ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway)); 2006 } 2007 jsonResponse["IPv6StaticDefaultGateways"] = 2008 std::move(ipv6StaticGatewayArray); 2009 2010 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 2011 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 2012 ipv6Array = nlohmann::json::array(); 2013 ipv6StaticArray = nlohmann::json::array(); 2014 nlohmann::json& ipv6AddrPolicyTable = 2015 jsonResponse["IPv6AddressPolicyTable"]; 2016 ipv6AddrPolicyTable = nlohmann::json::array(); 2017 for (const auto& ipv6Config : ipv6Data) 2018 { 2019 nlohmann::json::object_t ipv6; 2020 ipv6["Address"] = ipv6Config.address; 2021 ipv6["PrefixLength"] = ipv6Config.prefixLength; 2022 ipv6["AddressOrigin"] = ipv6Config.origin; 2023 2024 ipv6Array.emplace_back(std::move(ipv6)); 2025 if (ipv6Config.origin == "Static") 2026 { 2027 nlohmann::json::object_t ipv6Static; 2028 ipv6Static["Address"] = ipv6Config.address; 2029 ipv6Static["PrefixLength"] = ipv6Config.prefixLength; 2030 ipv6StaticArray.emplace_back(std::move(ipv6Static)); 2031 } 2032 } 2033 } 2034 2035 inline void afterDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2036 const std::string& ifaceId, 2037 const boost::system::error_code& ec, 2038 const sdbusplus::message_t& m) 2039 { 2040 if (!ec) 2041 { 2042 return; 2043 } 2044 const sd_bus_error* dbusError = m.get_error(); 2045 if (dbusError == nullptr) 2046 { 2047 messages::internalError(asyncResp->res); 2048 return; 2049 } 2050 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); 2051 2052 if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") == 2053 dbusError->name) 2054 { 2055 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2056 ifaceId); 2057 return; 2058 } 2059 if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") == 2060 dbusError->name) 2061 { 2062 messages::resourceCannotBeDeleted(asyncResp->res); 2063 return; 2064 } 2065 messages::internalError(asyncResp->res); 2066 } 2067 2068 inline void afterVlanCreate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2069 const std::string& parentInterfaceUri, 2070 const std::string& vlanInterface, 2071 const boost::system::error_code& ec, 2072 const sdbusplus::message_t& m 2073 2074 ) 2075 { 2076 if (ec) 2077 { 2078 const sd_bus_error* dbusError = m.get_error(); 2079 if (dbusError == nullptr) 2080 { 2081 messages::internalError(asyncResp->res); 2082 return; 2083 } 2084 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); 2085 2086 if (std::string_view( 2087 "xyz.openbmc_project.Common.Error.ResourceNotFound") == 2088 dbusError->name) 2089 { 2090 messages::propertyValueNotInList( 2091 asyncResp->res, parentInterfaceUri, 2092 "Links/RelatedInterfaces/0/@odata.id"); 2093 return; 2094 } 2095 if (std::string_view( 2096 "xyz.openbmc_project.Common.Error.InvalidArgument") == 2097 dbusError->name) 2098 { 2099 messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface", 2100 "Id", vlanInterface); 2101 return; 2102 } 2103 messages::internalError(asyncResp->res); 2104 return; 2105 } 2106 2107 const boost::urls::url vlanInterfaceUri = boost::urls::format( 2108 "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", vlanInterface); 2109 asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer()); 2110 } 2111 2112 inline void requestEthernetInterfacesRoutes(App& app) 2113 { 2114 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 2115 .privileges(redfish::privileges::getEthernetInterfaceCollection) 2116 .methods(boost::beast::http::verb::get)( 2117 [&app](const crow::Request& req, 2118 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2119 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2120 { 2121 return; 2122 } 2123 2124 asyncResp->res.jsonValue["@odata.type"] = 2125 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 2126 asyncResp->res.jsonValue["@odata.id"] = 2127 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 2128 asyncResp->res.jsonValue["Name"] = 2129 "Ethernet Network Interface Collection"; 2130 asyncResp->res.jsonValue["Description"] = 2131 "Collection of EthernetInterfaces for this Manager"; 2132 2133 // Get eth interface list, and call the below callback for JSON 2134 // preparation 2135 getEthernetIfaceList( 2136 [asyncResp](const bool& success, 2137 const std::vector<std::string>& ifaceList) { 2138 if (!success) 2139 { 2140 messages::internalError(asyncResp->res); 2141 return; 2142 } 2143 2144 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 2145 ifaceArray = nlohmann::json::array(); 2146 for (const std::string& ifaceItem : ifaceList) 2147 { 2148 nlohmann::json::object_t iface; 2149 iface["@odata.id"] = boost::urls::format( 2150 "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", 2151 ifaceItem); 2152 ifaceArray.push_back(std::move(iface)); 2153 } 2154 2155 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 2156 asyncResp->res.jsonValue["@odata.id"] = 2157 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 2158 }); 2159 }); 2160 2161 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 2162 .privileges(redfish::privileges::postEthernetInterfaceCollection) 2163 .methods(boost::beast::http::verb::post)( 2164 [&app](const crow::Request& req, 2165 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2166 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2167 { 2168 return; 2169 } 2170 2171 bool vlanEnable = false; 2172 uint32_t vlanId = 0; 2173 nlohmann::json::array_t relatedInterfaces; 2174 2175 if (!json_util::readJsonPatch(req, asyncResp->res, "VLAN/VLANEnable", 2176 vlanEnable, "VLAN/VLANId", vlanId, 2177 "Links/RelatedInterfaces", 2178 relatedInterfaces)) 2179 { 2180 return; 2181 } 2182 2183 if (relatedInterfaces.size() != 1) 2184 { 2185 messages::arraySizeTooLong(asyncResp->res, 2186 "Links/RelatedInterfaces", 2187 relatedInterfaces.size()); 2188 return; 2189 } 2190 2191 std::string parentInterfaceUri; 2192 if (!json_util::readJson(relatedInterfaces[0], asyncResp->res, 2193 "@odata.id", parentInterfaceUri)) 2194 { 2195 messages::propertyMissing(asyncResp->res, 2196 "Links/RelatedInterfaces/0/@odata.id"); 2197 return; 2198 } 2199 BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri); 2200 2201 boost::system::result<boost::urls::url_view> parsedUri = 2202 boost::urls::parse_relative_ref(parentInterfaceUri); 2203 if (!parsedUri) 2204 { 2205 messages::propertyValueFormatError( 2206 asyncResp->res, parentInterfaceUri, 2207 "Links/RelatedInterfaces/0/@odata.id"); 2208 return; 2209 } 2210 2211 std::string parentInterface; 2212 if (!crow::utility::readUrlSegments( 2213 *parsedUri, "redfish", "v1", "Managers", "bmc", 2214 "EthernetInterfaces", std::ref(parentInterface))) 2215 { 2216 messages::propertyValueNotInList( 2217 asyncResp->res, parentInterfaceUri, 2218 "Links/RelatedInterfaces/0/@odata.id"); 2219 return; 2220 } 2221 2222 if (!vlanEnable) 2223 { 2224 // In OpenBMC implementation, VLANEnable cannot be false on 2225 // create 2226 messages::propertyValueIncorrect(asyncResp->res, "VLAN/VLANEnable", 2227 "false"); 2228 return; 2229 } 2230 2231 std::string vlanInterface = parentInterface + "_" + 2232 std::to_string(vlanId); 2233 crow::connections::systemBus->async_method_call( 2234 [asyncResp, parentInterfaceUri, 2235 vlanInterface](const boost::system::error_code& ec, 2236 const sdbusplus::message_t& m) { 2237 afterVlanCreate(asyncResp, parentInterfaceUri, vlanInterface, ec, 2238 m); 2239 }, 2240 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 2241 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", parentInterface, 2242 vlanId); 2243 }); 2244 2245 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 2246 .privileges(redfish::privileges::getEthernetInterface) 2247 .methods(boost::beast::http::verb::get)( 2248 [&app](const crow::Request& req, 2249 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2250 const std::string& ifaceId) { 2251 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2252 { 2253 return; 2254 } 2255 getEthernetIfaceData( 2256 ifaceId, 2257 [asyncResp, 2258 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2259 const std::vector<IPv4AddressData>& ipv4Data, 2260 const std::vector<IPv6AddressData>& ipv6Data, 2261 const std::vector<StaticGatewayData>& ipv6GatewayData) { 2262 if (!success) 2263 { 2264 // TODO(Pawel)consider distinguish between non 2265 // existing object, and other errors 2266 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2267 ifaceId); 2268 return; 2269 } 2270 2271 asyncResp->res.jsonValue["@odata.type"] = 2272 "#EthernetInterface.v1_9_0.EthernetInterface"; 2273 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 2274 asyncResp->res.jsonValue["Description"] = 2275 "Management Network Interface"; 2276 2277 parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data, 2278 ipv6GatewayData); 2279 }); 2280 }); 2281 2282 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 2283 .privileges(redfish::privileges::patchEthernetInterface) 2284 .methods(boost::beast::http::verb::patch)( 2285 [&app](const crow::Request& req, 2286 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2287 const std::string& ifaceId) { 2288 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2289 { 2290 return; 2291 } 2292 std::optional<std::string> hostname; 2293 std::optional<std::string> fqdn; 2294 std::optional<std::string> macAddress; 2295 std::optional<std::string> ipv6DefaultGateway; 2296 std::optional<nlohmann::json::array_t> ipv4StaticAddresses; 2297 std::optional<nlohmann::json::array_t> ipv6StaticAddresses; 2298 std::optional<nlohmann::json::array_t> ipv6StaticDefaultGateways; 2299 std::optional<std::vector<std::string>> staticNameServers; 2300 std::optional<nlohmann::json> dhcpv4; 2301 std::optional<nlohmann::json> dhcpv6; 2302 std::optional<bool> ipv6AutoConfigEnabled; 2303 std::optional<bool> interfaceEnabled; 2304 std::optional<size_t> mtuSize; 2305 DHCPParameters v4dhcpParms; 2306 DHCPParameters v6dhcpParms; 2307 // clang-format off 2308 if (!json_util::readJsonPatch( 2309 req, asyncResp->res, 2310 "DHCPv4", dhcpv4, 2311 "DHCPv6", dhcpv6, 2312 "FQDN", fqdn, 2313 "HostName", hostname, 2314 "IPv4StaticAddresses", ipv4StaticAddresses, 2315 "IPv6DefaultGateway", ipv6DefaultGateway, 2316 "IPv6StaticAddresses", ipv6StaticAddresses, 2317 "IPv6StaticDefaultGateways", ipv6StaticDefaultGateways, 2318 "InterfaceEnabled", interfaceEnabled, 2319 "MACAddress", macAddress, 2320 "MTUSize", mtuSize, 2321 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", ipv6AutoConfigEnabled, 2322 "StaticNameServers", staticNameServers 2323 ) 2324 ) 2325 { 2326 return; 2327 } 2328 //clang-format on 2329 if (dhcpv4) 2330 { 2331 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 2332 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 2333 v4dhcpParms.useDnsServers, "UseNTPServers", 2334 v4dhcpParms.useNtpServers, "UseDomainName", 2335 v4dhcpParms.useDomainName)) 2336 { 2337 return; 2338 } 2339 } 2340 2341 if (dhcpv6) 2342 { 2343 if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode", 2344 v6dhcpParms.dhcpv6OperatingMode, 2345 "UseDNSServers", v6dhcpParms.useDnsServers, 2346 "UseNTPServers", v6dhcpParms.useNtpServers, 2347 "UseDomainName", 2348 v6dhcpParms.useDomainName)) 2349 { 2350 return; 2351 } 2352 } 2353 2354 // Get single eth interface data, and call the below callback 2355 // for JSON preparation 2356 getEthernetIfaceData( 2357 ifaceId, 2358 [asyncResp, ifaceId, hostname = std::move(hostname), 2359 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 2360 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 2361 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 2362 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 2363 ipv6StaticDefaultGateway = std::move(ipv6StaticDefaultGateways), 2364 staticNameServers = std::move(staticNameServers), 2365 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize, 2366 ipv6AutoConfigEnabled, v4dhcpParms = std::move(v4dhcpParms), 2367 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 2368 const bool& success, const EthernetInterfaceData& ethData, 2369 const std::vector<IPv4AddressData>& ipv4Data, 2370 const std::vector<IPv6AddressData>& ipv6Data, 2371 const std::vector<StaticGatewayData>& ipv6GatewayData) { 2372 if (!success) 2373 { 2374 // ... otherwise return error 2375 // TODO(Pawel)consider distinguish between non 2376 // existing object, and other errors 2377 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2378 ifaceId); 2379 return; 2380 } 2381 2382 if (dhcpv4 || dhcpv6) 2383 { 2384 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, 2385 asyncResp); 2386 } 2387 2388 if (hostname) 2389 { 2390 handleHostnamePatch(*hostname, asyncResp); 2391 } 2392 2393 if (ipv6AutoConfigEnabled) 2394 { 2395 handleSLAACAutoConfigPatch(ifaceId, *ipv6AutoConfigEnabled, 2396 asyncResp); 2397 } 2398 2399 if (fqdn) 2400 { 2401 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 2402 } 2403 2404 if (macAddress) 2405 { 2406 handleMACAddressPatch(ifaceId, *macAddress, asyncResp); 2407 } 2408 2409 if (ipv4StaticAddresses) 2410 { 2411 // TODO(ed) for some reason the capture of 2412 // ipv4Addresses above is returning a const value, 2413 // not a non-const value. This doesn't really work 2414 // for us, as we need to be able to efficiently move 2415 // out the intermedia nlohmann::json objects. This 2416 // makes a copy of the structure, and operates on 2417 // that, but could be done more efficiently 2418 nlohmann::json::array_t ipv4Static = *ipv4StaticAddresses; 2419 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp); 2420 } 2421 2422 if (staticNameServers) 2423 { 2424 handleStaticNameServersPatch(ifaceId, *staticNameServers, 2425 asyncResp); 2426 } 2427 2428 if (ipv6DefaultGateway) 2429 { 2430 messages::propertyNotWritable(asyncResp->res, 2431 "IPv6DefaultGateway"); 2432 } 2433 2434 if (ipv6StaticAddresses) 2435 { 2436 handleIPv6StaticAddressesPatch(ifaceId, *ipv6StaticAddresses, 2437 ipv6Data, asyncResp); 2438 } 2439 2440 if (ipv6StaticDefaultGateway) 2441 { 2442 handleIPv6DefaultGateway(ifaceId, *ipv6StaticDefaultGateway, 2443 ipv6GatewayData, asyncResp); 2444 } 2445 2446 if (interfaceEnabled) 2447 { 2448 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled", 2449 *interfaceEnabled, asyncResp); 2450 } 2451 2452 if (mtuSize) 2453 { 2454 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 2455 } 2456 }); 2457 }); 2458 2459 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 2460 .privileges(redfish::privileges::deleteEthernetInterface) 2461 .methods(boost::beast::http::verb::delete_)( 2462 [&app](const crow::Request& req, 2463 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2464 const std::string& ifaceId) { 2465 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2466 { 2467 return; 2468 } 2469 2470 crow::connections::systemBus->async_method_call( 2471 [asyncResp, ifaceId](const boost::system::error_code& ec, 2472 const sdbusplus::message_t& m) { 2473 afterDelete(asyncResp, ifaceId, ec, m); 2474 }, 2475 "xyz.openbmc_project.Network", 2476 std::string("/xyz/openbmc_project/network/") + ifaceId, 2477 "xyz.openbmc_project.Object.Delete", "Delete"); 2478 }); 2479 } 2480 2481 } // namespace redfish 2482