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