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