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