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