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