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::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 184 translateAddressOriginDbusToRedfish(const std::string& inputOrigin, 185 bool isIPv4) 186 { 187 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") 188 { 189 return "Static"; 190 } 191 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") 192 { 193 if (isIPv4) 194 { 195 return "IPv4LinkLocal"; 196 } 197 return "LinkLocal"; 198 } 199 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") 200 { 201 if (isIPv4) 202 { 203 return "DHCP"; 204 } 205 return "DHCPv6"; 206 } 207 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") 208 { 209 return "SLAAC"; 210 } 211 return ""; 212 } 213 214 inline bool extractEthernetInterfaceData( 215 const std::string& ethifaceId, 216 const dbus::utility::ManagedObjectType& dbusData, 217 EthernetInterfaceData& ethData) 218 { 219 bool idFound = false; 220 for (const auto& objpath : dbusData) 221 { 222 for (const auto& ifacePair : objpath.second) 223 { 224 if (objpath.first == "/xyz/openbmc_project/network/" + ethifaceId) 225 { 226 idFound = true; 227 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 228 { 229 for (const auto& propertyPair : ifacePair.second) 230 { 231 if (propertyPair.first == "MACAddress") 232 { 233 const std::string* mac = 234 std::get_if<std::string>(&propertyPair.second); 235 if (mac != nullptr) 236 { 237 ethData.macAddress = *mac; 238 } 239 } 240 } 241 } 242 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") 243 { 244 for (const auto& propertyPair : ifacePair.second) 245 { 246 if (propertyPair.first == "Id") 247 { 248 const uint32_t* id = 249 std::get_if<uint32_t>(&propertyPair.second); 250 if (id != nullptr) 251 { 252 ethData.vlanId = *id; 253 } 254 } 255 } 256 } 257 else if (ifacePair.first == 258 "xyz.openbmc_project.Network.EthernetInterface") 259 { 260 for (const auto& propertyPair : ifacePair.second) 261 { 262 if (propertyPair.first == "AutoNeg") 263 { 264 const bool* autoNeg = 265 std::get_if<bool>(&propertyPair.second); 266 if (autoNeg != nullptr) 267 { 268 ethData.autoNeg = *autoNeg; 269 } 270 } 271 else if (propertyPair.first == "Speed") 272 { 273 const uint32_t* speed = 274 std::get_if<uint32_t>(&propertyPair.second); 275 if (speed != nullptr) 276 { 277 ethData.speed = *speed; 278 } 279 } 280 else if (propertyPair.first == "MTU") 281 { 282 const size_t* mtuSize = 283 std::get_if<size_t>(&propertyPair.second); 284 if (mtuSize != nullptr) 285 { 286 ethData.mtuSize = *mtuSize; 287 } 288 } 289 else if (propertyPair.first == "LinkUp") 290 { 291 const bool* linkUp = 292 std::get_if<bool>(&propertyPair.second); 293 if (linkUp != nullptr) 294 { 295 ethData.linkUp = *linkUp; 296 } 297 } 298 else if (propertyPair.first == "NICEnabled") 299 { 300 const bool* nicEnabled = 301 std::get_if<bool>(&propertyPair.second); 302 if (nicEnabled != nullptr) 303 { 304 ethData.nicEnabled = *nicEnabled; 305 } 306 } 307 else if (propertyPair.first == "IPv6AcceptRA") 308 { 309 const bool* ipv6AcceptRa = 310 std::get_if<bool>(&propertyPair.second); 311 if (ipv6AcceptRa != nullptr) 312 { 313 ethData.ipv6AcceptRa = *ipv6AcceptRa; 314 } 315 } 316 else if (propertyPair.first == "Nameservers") 317 { 318 const std::vector<std::string>* nameservers = 319 std::get_if<std::vector<std::string>>( 320 &propertyPair.second); 321 if (nameservers != nullptr) 322 { 323 ethData.nameServers = *nameservers; 324 } 325 } 326 else if (propertyPair.first == "StaticNameServers") 327 { 328 const std::vector<std::string>* staticNameServers = 329 std::get_if<std::vector<std::string>>( 330 &propertyPair.second); 331 if (staticNameServers != nullptr) 332 { 333 ethData.staticNameServers = *staticNameServers; 334 } 335 } 336 else if (propertyPair.first == "DHCPEnabled") 337 { 338 const std::string* dhcpEnabled = 339 std::get_if<std::string>(&propertyPair.second); 340 if (dhcpEnabled != nullptr) 341 { 342 ethData.dhcpEnabled = *dhcpEnabled; 343 } 344 } 345 else if (propertyPair.first == "DomainName") 346 { 347 const std::vector<std::string>* domainNames = 348 std::get_if<std::vector<std::string>>( 349 &propertyPair.second); 350 if (domainNames != nullptr) 351 { 352 ethData.domainnames = *domainNames; 353 } 354 } 355 else if (propertyPair.first == "DefaultGateway") 356 { 357 const std::string* defaultGateway = 358 std::get_if<std::string>(&propertyPair.second); 359 if (defaultGateway != nullptr) 360 { 361 std::string defaultGatewayStr = *defaultGateway; 362 if (defaultGatewayStr.empty()) 363 { 364 ethData.defaultGateway = "0.0.0.0"; 365 } 366 else 367 { 368 ethData.defaultGateway = defaultGatewayStr; 369 } 370 } 371 } 372 else if (propertyPair.first == "DefaultGateway6") 373 { 374 const std::string* defaultGateway6 = 375 std::get_if<std::string>(&propertyPair.second); 376 if (defaultGateway6 != nullptr) 377 { 378 std::string defaultGateway6Str = 379 *defaultGateway6; 380 if (defaultGateway6Str.empty()) 381 { 382 ethData.ipv6DefaultGateway = 383 "0:0:0:0:0:0:0:0"; 384 } 385 else 386 { 387 ethData.ipv6DefaultGateway = 388 defaultGateway6Str; 389 } 390 } 391 } 392 } 393 } 394 } 395 396 sdbusplus::message::object_path path( 397 "/xyz/openbmc_project/network"); 398 sdbusplus::message::object_path dhcp4Path = path / ethifaceId / 399 "dhcp4"; 400 401 if (sdbusplus::message::object_path(objpath.first) == dhcp4Path) 402 { 403 if (ifacePair.first == 404 "xyz.openbmc_project.Network.DHCPConfiguration") 405 { 406 for (const auto& propertyPair : ifacePair.second) 407 { 408 if (propertyPair.first == "DNSEnabled") 409 { 410 const bool* dnsEnabled = 411 std::get_if<bool>(&propertyPair.second); 412 if (dnsEnabled != nullptr) 413 { 414 ethData.dnsv4Enabled = *dnsEnabled; 415 } 416 } 417 else if (propertyPair.first == "DomainEnabled") 418 { 419 const bool* domainEnabled = 420 std::get_if<bool>(&propertyPair.second); 421 if (domainEnabled != nullptr) 422 { 423 ethData.domainv4Enabled = *domainEnabled; 424 } 425 } 426 else if (propertyPair.first == "NTPEnabled") 427 { 428 const bool* ntpEnabled = 429 std::get_if<bool>(&propertyPair.second); 430 if (ntpEnabled != nullptr) 431 { 432 ethData.ntpv4Enabled = *ntpEnabled; 433 } 434 } 435 else if (propertyPair.first == "HostNameEnabled") 436 { 437 const bool* hostNameEnabled = 438 std::get_if<bool>(&propertyPair.second); 439 if (hostNameEnabled != nullptr) 440 { 441 ethData.hostNamev4Enabled = *hostNameEnabled; 442 } 443 } 444 } 445 } 446 } 447 448 sdbusplus::message::object_path dhcp6Path = path / ethifaceId / 449 "dhcp6"; 450 451 if (sdbusplus::message::object_path(objpath.first) == dhcp6Path) 452 { 453 if (ifacePair.first == 454 "xyz.openbmc_project.Network.DHCPConfiguration") 455 { 456 for (const auto& propertyPair : ifacePair.second) 457 { 458 if (propertyPair.first == "DNSEnabled") 459 { 460 const bool* dnsEnabled = 461 std::get_if<bool>(&propertyPair.second); 462 if (dnsEnabled != nullptr) 463 { 464 ethData.dnsv6Enabled = *dnsEnabled; 465 } 466 } 467 if (propertyPair.first == "DomainEnabled") 468 { 469 const bool* domainEnabled = 470 std::get_if<bool>(&propertyPair.second); 471 if (domainEnabled != nullptr) 472 { 473 ethData.domainv6Enabled = *domainEnabled; 474 } 475 } 476 else if (propertyPair.first == "NTPEnabled") 477 { 478 const bool* ntpEnabled = 479 std::get_if<bool>(&propertyPair.second); 480 if (ntpEnabled != nullptr) 481 { 482 ethData.ntpv6Enabled = *ntpEnabled; 483 } 484 } 485 else if (propertyPair.first == "HostNameEnabled") 486 { 487 const bool* hostNameEnabled = 488 std::get_if<bool>(&propertyPair.second); 489 if (hostNameEnabled != nullptr) 490 { 491 ethData.hostNamev6Enabled = *hostNameEnabled; 492 } 493 } 494 } 495 } 496 } 497 // System configuration shows up in the global namespace, so no need 498 // to check eth number 499 if (ifacePair.first == 500 "xyz.openbmc_project.Network.SystemConfiguration") 501 { 502 for (const auto& propertyPair : ifacePair.second) 503 { 504 if (propertyPair.first == "HostName") 505 { 506 const std::string* hostname = 507 std::get_if<std::string>(&propertyPair.second); 508 if (hostname != nullptr) 509 { 510 ethData.hostName = *hostname; 511 } 512 } 513 } 514 } 515 } 516 } 517 return idFound; 518 } 519 520 // Helper function that extracts data for single ethernet ipv6 address 521 inline void extractIPV6Data(const std::string& ethifaceId, 522 const dbus::utility::ManagedObjectType& dbusData, 523 std::vector<IPv6AddressData>& ipv6Config) 524 { 525 const std::string ipPathStart = "/xyz/openbmc_project/network/" + 526 ethifaceId; 527 528 // Since there might be several IPv6 configurations aligned with 529 // single ethernet interface, loop over all of them 530 for (const auto& objpath : dbusData) 531 { 532 // Check if proper pattern for object path appears 533 if (objpath.first.str.starts_with(ipPathStart + "/")) 534 { 535 for (const auto& interface : objpath.second) 536 { 537 if (interface.first == "xyz.openbmc_project.Network.IP") 538 { 539 auto type = std::ranges::find_if(interface.second, 540 [](const auto& property) { 541 return property.first == "Type"; 542 }); 543 if (type == interface.second.end()) 544 { 545 continue; 546 } 547 548 const std::string* typeStr = 549 std::get_if<std::string>(&type->second); 550 551 if (typeStr == nullptr || 552 (*typeStr != 553 "xyz.openbmc_project.Network.IP.Protocol.IPv6")) 554 { 555 continue; 556 } 557 558 // Instance IPv6AddressData structure, and set as 559 // appropriate 560 IPv6AddressData& ipv6Address = ipv6Config.emplace_back(); 561 ipv6Address.id = 562 objpath.first.str.substr(ipPathStart.size()); 563 for (const auto& property : interface.second) 564 { 565 if (property.first == "Address") 566 { 567 const std::string* address = 568 std::get_if<std::string>(&property.second); 569 if (address != nullptr) 570 { 571 ipv6Address.address = *address; 572 } 573 } 574 else if (property.first == "Origin") 575 { 576 const std::string* origin = 577 std::get_if<std::string>(&property.second); 578 if (origin != nullptr) 579 { 580 ipv6Address.origin = 581 translateAddressOriginDbusToRedfish(*origin, 582 false); 583 } 584 } 585 else if (property.first == "PrefixLength") 586 { 587 const uint8_t* prefix = 588 std::get_if<uint8_t>(&property.second); 589 if (prefix != nullptr) 590 { 591 ipv6Address.prefixLength = *prefix; 592 } 593 } 594 else if (property.first == "Type" || 595 property.first == "Gateway") 596 { 597 // Type & Gateway is not used 598 } 599 else 600 { 601 BMCWEB_LOG_ERROR( 602 "Got extra property: {} on the {} object", 603 property.first, objpath.first.str); 604 } 605 } 606 } 607 } 608 } 609 } 610 } 611 612 // Helper function that extracts data for single ethernet ipv4 address 613 inline void extractIPData(const std::string& ethifaceId, 614 const dbus::utility::ManagedObjectType& dbusData, 615 std::vector<IPv4AddressData>& ipv4Config) 616 { 617 const std::string ipPathStart = "/xyz/openbmc_project/network/" + 618 ethifaceId; 619 620 // Since there might be several IPv4 configurations aligned with 621 // single ethernet interface, loop over all of them 622 for (const auto& objpath : dbusData) 623 { 624 // Check if proper pattern for object path appears 625 if (objpath.first.str.starts_with(ipPathStart + "/")) 626 { 627 for (const auto& interface : objpath.second) 628 { 629 if (interface.first == "xyz.openbmc_project.Network.IP") 630 { 631 auto type = std::ranges::find_if(interface.second, 632 [](const auto& property) { 633 return property.first == "Type"; 634 }); 635 if (type == interface.second.end()) 636 { 637 continue; 638 } 639 640 const std::string* typeStr = 641 std::get_if<std::string>(&type->second); 642 643 if (typeStr == nullptr || 644 (*typeStr != 645 "xyz.openbmc_project.Network.IP.Protocol.IPv4")) 646 { 647 continue; 648 } 649 650 // Instance IPv4AddressData structure, and set as 651 // appropriate 652 IPv4AddressData& ipv4Address = ipv4Config.emplace_back(); 653 ipv4Address.id = 654 objpath.first.str.substr(ipPathStart.size()); 655 for (const auto& property : interface.second) 656 { 657 if (property.first == "Address") 658 { 659 const std::string* address = 660 std::get_if<std::string>(&property.second); 661 if (address != nullptr) 662 { 663 ipv4Address.address = *address; 664 } 665 } 666 else if (property.first == "Origin") 667 { 668 const std::string* origin = 669 std::get_if<std::string>(&property.second); 670 if (origin != nullptr) 671 { 672 ipv4Address.origin = 673 translateAddressOriginDbusToRedfish(*origin, 674 true); 675 } 676 } 677 else if (property.first == "PrefixLength") 678 { 679 const uint8_t* mask = 680 std::get_if<uint8_t>(&property.second); 681 if (mask != nullptr) 682 { 683 // convert it to the string 684 ipv4Address.netmask = getNetmask(*mask); 685 } 686 } 687 else if (property.first == "Type" || 688 property.first == "Gateway") 689 { 690 // Type & Gateway is not used 691 } 692 else 693 { 694 BMCWEB_LOG_ERROR( 695 "Got extra property: {} on the {} object", 696 property.first, objpath.first.str); 697 } 698 } 699 // Check if given address is local, or global 700 ipv4Address.linktype = 701 ipv4Address.address.starts_with("169.254.") 702 ? LinkType::Local 703 : LinkType::Global; 704 } 705 } 706 } 707 } 708 } 709 710 /** 711 * @brief Modifies the default gateway assigned to the NIC 712 * 713 * @param[in] ifaceId Id of network interface whose default gateway is to be 714 * changed 715 * @param[in] gateway The new gateway value. Assigning an empty string 716 * causes the gateway to be deleted 717 * @param[io] asyncResp Response object that will be returned to client 718 * 719 * @return None 720 */ 721 inline void updateIPv4DefaultGateway( 722 const std::string& ifaceId, const std::string& gateway, 723 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 724 { 725 setDbusProperty( 726 asyncResp, "Gateway", "xyz.openbmc_project.Network", 727 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 728 ifaceId, 729 "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway", 730 gateway); 731 } 732 733 /** 734 * @brief Deletes given static IP address for the interface 735 * 736 * @param[in] ifaceId Id of interface whose IP should be deleted 737 * @param[in] ipHash DBus Hash id of IP that should be deleted 738 * @param[io] asyncResp Response object that will be returned to client 739 * 740 * @return None 741 */ 742 inline void deleteIPAddress(const std::string& ifaceId, 743 const std::string& ipHash, 744 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 745 { 746 crow::connections::systemBus->async_method_call( 747 [asyncResp](const boost::system::error_code& ec) { 748 if (ec) 749 { 750 messages::internalError(asyncResp->res); 751 } 752 }, 753 "xyz.openbmc_project.Network", 754 "/xyz/openbmc_project/network/" + ifaceId + ipHash, 755 "xyz.openbmc_project.Object.Delete", "Delete"); 756 } 757 758 /** 759 * @brief Creates a static IPv4 entry 760 * 761 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 762 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 763 * @param[in] gateway IPv4 address of this interfaces gateway 764 * @param[in] address IPv4 address to assign to this interface 765 * @param[io] asyncResp Response object that will be returned to client 766 * 767 * @return None 768 */ 769 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength, 770 const std::string& gateway, const std::string& address, 771 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 772 { 773 auto createIpHandler = [asyncResp, ifaceId, 774 gateway](const boost::system::error_code& ec) { 775 if (ec) 776 { 777 messages::internalError(asyncResp->res); 778 return; 779 } 780 }; 781 782 crow::connections::systemBus->async_method_call( 783 std::move(createIpHandler), "xyz.openbmc_project.Network", 784 "/xyz/openbmc_project/network/" + ifaceId, 785 "xyz.openbmc_project.Network.IP.Create", "IP", 786 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, 787 gateway); 788 } 789 790 /** 791 * @brief Deletes the IP entry for this interface and creates a replacement 792 * static entry 793 * 794 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 795 * @param[in] id The unique hash entry identifying the DBus entry 796 * @param[in] prefixLength Prefix syntax for the subnet mask 797 * @param[in] address Address to assign to this interface 798 * @param[in] numStaticAddrs Count of IPv4 static addresses 799 * @param[io] asyncResp Response object that will be returned to client 800 * 801 * @return None 802 */ 803 804 inline void deleteAndCreateIPAddress( 805 IpVersion version, const std::string& ifaceId, const std::string& id, 806 uint8_t prefixLength, const std::string& address, 807 const std::string& gateway, 808 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 809 { 810 crow::connections::systemBus->async_method_call( 811 [asyncResp, version, ifaceId, address, prefixLength, 812 gateway](const boost::system::error_code& ec) { 813 if (ec) 814 { 815 messages::internalError(asyncResp->res); 816 } 817 std::string protocol = "xyz.openbmc_project.Network.IP.Protocol."; 818 protocol += version == IpVersion::IpV4 ? "IPv4" : "IPv6"; 819 crow::connections::systemBus->async_method_call( 820 [asyncResp](const boost::system::error_code& ec2) { 821 if (ec2) 822 { 823 messages::internalError(asyncResp->res); 824 } 825 }, 826 "xyz.openbmc_project.Network", 827 "/xyz/openbmc_project/network/" + ifaceId, 828 "xyz.openbmc_project.Network.IP.Create", "IP", protocol, address, 829 prefixLength, gateway); 830 }, 831 "xyz.openbmc_project.Network", 832 "/xyz/openbmc_project/network/" + ifaceId + id, 833 "xyz.openbmc_project.Object.Delete", "Delete"); 834 } 835 836 inline bool extractIPv6DefaultGatewayData( 837 const std::string& ethifaceId, 838 const dbus::utility::ManagedObjectType& dbusData, 839 std::vector<StaticGatewayData>& staticGatewayConfig) 840 { 841 std::string staticGatewayPathStart("/xyz/openbmc_project/network/"); 842 staticGatewayPathStart += ethifaceId; 843 844 for (const auto& objpath : dbusData) 845 { 846 if (!std::string_view(objpath.first.str) 847 .starts_with(staticGatewayPathStart)) 848 { 849 continue; 850 } 851 for (const auto& interface : objpath.second) 852 { 853 if (interface.first != "xyz.openbmc_project.Network.StaticGateway") 854 { 855 continue; 856 } 857 StaticGatewayData& staticGateway = 858 staticGatewayConfig.emplace_back(); 859 staticGateway.id = objpath.first.filename(); 860 861 bool success = sdbusplus::unpackPropertiesNoThrow( 862 redfish::dbus_utils::UnpackErrorPrinter(), interface.second, 863 "Gateway", staticGateway.gateway, "PrefixLength", 864 staticGateway.prefixLength, "ProtocolType", 865 staticGateway.protocol); 866 if (!success) 867 { 868 return false; 869 } 870 } 871 } 872 return true; 873 } 874 875 /** 876 * @brief Creates IPv6 with given data 877 * 878 * @param[in] ifaceId Id of interface whose IP should be added 879 * @param[in] prefixLength Prefix length that needs to be added 880 * @param[in] address IP address that needs to be added 881 * @param[io] asyncResp Response object that will be returned to client 882 * 883 * @return None 884 */ 885 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength, 886 const std::string& address, 887 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 888 { 889 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 890 path /= ifaceId; 891 892 auto createIpHandler = [asyncResp, 893 address](const boost::system::error_code& ec) { 894 if (ec) 895 { 896 if (ec == boost::system::errc::io_error) 897 { 898 messages::propertyValueFormatError(asyncResp->res, address, 899 "Address"); 900 } 901 else 902 { 903 messages::internalError(asyncResp->res); 904 } 905 } 906 }; 907 // Passing null for gateway, as per redfish spec IPv6StaticAddresses 908 // object does not have associated gateway property 909 crow::connections::systemBus->async_method_call( 910 std::move(createIpHandler), "xyz.openbmc_project.Network", path, 911 "xyz.openbmc_project.Network.IP.Create", "IP", 912 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 913 ""); 914 } 915 916 /** 917 * @brief Deletes given IPv6 Static Gateway 918 * 919 * @param[in] ifaceId Id of interface whose IP should be deleted 920 * @param[in] ipHash DBus Hash id of IP that should be deleted 921 * @param[io] asyncResp Response object that will be returned to client 922 * 923 * @return None 924 */ 925 inline void 926 deleteIPv6Gateway(std::string_view gatewayId, 927 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 928 { 929 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 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 /= gatewayId; 989 crow::connections::systemBus->async_method_call( 990 [asyncResp, ifaceId, gateway, 991 prefixLength](const boost::system::error_code& ec) { 992 if (ec) 993 { 994 messages::internalError(asyncResp->res); 995 return; 996 } 997 createIPv6DefaultGateway(ifaceId, prefixLength, gateway, asyncResp); 998 }, 999 "xyz.openbmc_project.Network", path, 1000 "xyz.openbmc_project.Object.Delete", "Delete"); 1001 } 1002 1003 /** 1004 * @brief Sets IPv6 default gateway with given data 1005 * 1006 * @param[in] ifaceId Id of interface whose gateway should be added 1007 * @param[in] input Contains address that needs to be added 1008 * @param[in] staticGatewayData Current static gateways in the system 1009 * @param[io] asyncResp Response object that will be returned to client 1010 * 1011 * @return None 1012 */ 1013 1014 inline void handleIPv6DefaultGateway( 1015 const std::string& ifaceId, 1016 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>& input, 1017 const std::vector<StaticGatewayData>& staticGatewayData, 1018 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1019 { 1020 size_t entryIdx = 1; 1021 std::vector<StaticGatewayData>::const_iterator staticGatewayEntry = 1022 staticGatewayData.begin(); 1023 1024 for (std::variant<nlohmann::json::object_t, std::nullptr_t>& thisJson : 1025 input) 1026 { 1027 // find the next gateway entry 1028 while (staticGatewayEntry != staticGatewayData.end()) 1029 { 1030 if (staticGatewayEntry->protocol == 1031 "xyz.openbmc_project.Network.IP.Protocol.IPv6") 1032 { 1033 break; 1034 } 1035 staticGatewayEntry++; 1036 } 1037 std::string pathString = "IPv6StaticDefaultGateways/" + 1038 std::to_string(entryIdx); 1039 nlohmann::json::object_t* obj = 1040 std::get_if<nlohmann::json::object_t>(&thisJson); 1041 if (obj == nullptr) 1042 { 1043 if (staticGatewayEntry == staticGatewayData.end()) 1044 { 1045 messages::resourceCannotBeDeleted(asyncResp->res); 1046 return; 1047 } 1048 deleteIPv6Gateway(staticGatewayEntry->id, asyncResp); 1049 return; 1050 } 1051 if (obj->empty()) 1052 { 1053 // Do nothing, but make sure the entry exists. 1054 if (staticGatewayEntry == staticGatewayData.end()) 1055 { 1056 messages::propertyValueFormatError(asyncResp->res, *obj, 1057 pathString); 1058 return; 1059 } 1060 } 1061 std::optional<std::string> address; 1062 std::optional<size_t> prefixLength; 1063 1064 if (!json_util::readJsonObject(*obj, asyncResp->res, "Address", address, 1065 "PrefixLength", prefixLength)) 1066 { 1067 return; 1068 } 1069 const std::string* addr = nullptr; 1070 size_t prefix = 0; 1071 if (address) 1072 { 1073 addr = &(*address); 1074 } 1075 else if (staticGatewayEntry != staticGatewayData.end()) 1076 { 1077 addr = &(staticGatewayEntry->gateway); 1078 } 1079 else 1080 { 1081 messages::propertyMissing(asyncResp->res, pathString + "/Address"); 1082 return; 1083 } 1084 if (prefixLength) 1085 { 1086 prefix = *prefixLength; 1087 } 1088 else if (staticGatewayEntry != staticGatewayData.end()) 1089 { 1090 prefix = staticGatewayEntry->prefixLength; 1091 } 1092 else 1093 { 1094 messages::propertyMissing(asyncResp->res, 1095 pathString + "/PrefixLength"); 1096 return; 1097 } 1098 if (staticGatewayEntry != staticGatewayData.end()) 1099 { 1100 deleteAndCreateIPv6DefaultGateway(ifaceId, staticGatewayEntry->id, 1101 *addr, prefix, asyncResp); 1102 staticGatewayEntry++; 1103 } 1104 else 1105 { 1106 createIPv6DefaultGateway(ifaceId, prefix, *addr, asyncResp); 1107 } 1108 entryIdx++; 1109 } 1110 } 1111 1112 /** 1113 * Function that retrieves all properties for given Ethernet Interface 1114 * Object 1115 * from EntityManager Network Manager 1116 * @param ethiface_id a eth interface id to query on DBus 1117 * @param callback a function that shall be called to convert Dbus output 1118 * into JSON 1119 */ 1120 template <typename CallbackFunc> 1121 void getEthernetIfaceData(const std::string& ethifaceId, 1122 CallbackFunc&& callback) 1123 { 1124 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1125 dbus::utility::getManagedObjects( 1126 "xyz.openbmc_project.Network", path, 1127 [ethifaceId{std::string{ethifaceId}}, 1128 callback = std::forward<CallbackFunc>(callback)]( 1129 const boost::system::error_code& ec, 1130 const dbus::utility::ManagedObjectType& resp) mutable { 1131 EthernetInterfaceData ethData{}; 1132 std::vector<IPv4AddressData> ipv4Data; 1133 std::vector<IPv6AddressData> ipv6Data; 1134 std::vector<StaticGatewayData> ipv6GatewayData; 1135 1136 if (ec) 1137 { 1138 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1139 return; 1140 } 1141 1142 bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData); 1143 if (!found) 1144 { 1145 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1146 return; 1147 } 1148 1149 extractIPData(ethifaceId, resp, ipv4Data); 1150 // Fix global GW 1151 for (IPv4AddressData& ipv4 : ipv4Data) 1152 { 1153 if (((ipv4.linktype == LinkType::Global) && 1154 (ipv4.gateway == "0.0.0.0")) || 1155 (ipv4.origin == "DHCP") || (ipv4.origin == "Static")) 1156 { 1157 ipv4.gateway = ethData.defaultGateway; 1158 } 1159 } 1160 1161 extractIPV6Data(ethifaceId, resp, ipv6Data); 1162 if (!extractIPv6DefaultGatewayData(ethifaceId, resp, ipv6GatewayData)) 1163 { 1164 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1165 } 1166 // Finally make a callback with useful data 1167 callback(true, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1168 }); 1169 } 1170 1171 /** 1172 * Function that retrieves all Ethernet Interfaces available through Network 1173 * Manager 1174 * @param callback a function that shall be called to convert Dbus output 1175 * into JSON. 1176 */ 1177 template <typename CallbackFunc> 1178 void getEthernetIfaceList(CallbackFunc&& callback) 1179 { 1180 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1181 dbus::utility::getManagedObjects( 1182 "xyz.openbmc_project.Network", path, 1183 [callback = std::forward<CallbackFunc>(callback)]( 1184 const boost::system::error_code& ec, 1185 const dbus::utility::ManagedObjectType& resp) { 1186 // Callback requires vector<string> to retrieve all available 1187 // ethernet interfaces 1188 std::vector<std::string> ifaceList; 1189 ifaceList.reserve(resp.size()); 1190 if (ec) 1191 { 1192 callback(false, ifaceList); 1193 return; 1194 } 1195 1196 // Iterate over all retrieved ObjectPaths. 1197 for (const auto& objpath : resp) 1198 { 1199 // And all interfaces available for certain ObjectPath. 1200 for (const auto& interface : objpath.second) 1201 { 1202 // If interface is 1203 // xyz.openbmc_project.Network.EthernetInterface, this is 1204 // what we're looking for. 1205 if (interface.first == 1206 "xyz.openbmc_project.Network.EthernetInterface") 1207 { 1208 std::string ifaceId = objpath.first.filename(); 1209 if (ifaceId.empty()) 1210 { 1211 continue; 1212 } 1213 // and put it into output vector. 1214 ifaceList.emplace_back(ifaceId); 1215 } 1216 } 1217 } 1218 1219 std::ranges::sort(ifaceList, AlphanumLess<std::string>()); 1220 1221 // Finally make a callback with useful data 1222 callback(true, ifaceList); 1223 }); 1224 } 1225 1226 inline void 1227 handleHostnamePatch(const std::string& hostname, 1228 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1229 { 1230 // SHOULD handle host names of up to 255 characters(RFC 1123) 1231 if (hostname.length() > 255) 1232 { 1233 messages::propertyValueFormatError(asyncResp->res, hostname, 1234 "HostName"); 1235 return; 1236 } 1237 setDbusProperty( 1238 asyncResp, "HostName", "xyz.openbmc_project.Network", 1239 sdbusplus::message::object_path("/xyz/openbmc_project/network/config"), 1240 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 1241 hostname); 1242 } 1243 1244 inline void 1245 handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize, 1246 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1247 { 1248 sdbusplus::message::object_path objPath("/xyz/openbmc_project/network"); 1249 objPath /= ifaceId; 1250 setDbusProperty(asyncResp, "MTUSize", "xyz.openbmc_project.Network", 1251 objPath, "xyz.openbmc_project.Network.EthernetInterface", 1252 "MTU", mtuSize); 1253 } 1254 1255 inline void 1256 handleDomainnamePatch(const std::string& ifaceId, 1257 const std::string& domainname, 1258 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1259 { 1260 std::vector<std::string> vectorDomainname = {domainname}; 1261 setDbusProperty( 1262 asyncResp, "FQDN", "xyz.openbmc_project.Network", 1263 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 1264 ifaceId, 1265 "xyz.openbmc_project.Network.EthernetInterface", "DomainName", 1266 vectorDomainname); 1267 } 1268 1269 inline bool isHostnameValid(const std::string& hostname) 1270 { 1271 // A valid host name can never have the dotted-decimal form (RFC 1123) 1272 if (std::ranges::all_of(hostname, ::isdigit)) 1273 { 1274 return false; 1275 } 1276 // Each label(hostname/subdomains) within a valid FQDN 1277 // MUST handle host names of up to 63 characters (RFC 1123) 1278 // labels cannot start or end with hyphens (RFC 952) 1279 // labels can start with numbers (RFC 1123) 1280 const static std::regex pattern( 1281 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 1282 1283 return std::regex_match(hostname, pattern); 1284 } 1285 1286 inline bool isDomainnameValid(const std::string& domainname) 1287 { 1288 // Can have multiple subdomains 1289 // Top Level Domain's min length is 2 character 1290 const static std::regex pattern( 1291 "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$"); 1292 1293 return std::regex_match(domainname, pattern); 1294 } 1295 1296 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn, 1297 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1298 { 1299 // Total length of FQDN must not exceed 255 characters(RFC 1035) 1300 if (fqdn.length() > 255) 1301 { 1302 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1303 return; 1304 } 1305 1306 size_t pos = fqdn.find('.'); 1307 if (pos == std::string::npos) 1308 { 1309 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1310 return; 1311 } 1312 1313 std::string hostname; 1314 std::string domainname; 1315 domainname = (fqdn).substr(pos + 1); 1316 hostname = (fqdn).substr(0, pos); 1317 1318 if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) 1319 { 1320 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1321 return; 1322 } 1323 1324 handleHostnamePatch(hostname, asyncResp); 1325 handleDomainnamePatch(ifaceId, domainname, asyncResp); 1326 } 1327 1328 inline void 1329 handleMACAddressPatch(const std::string& ifaceId, 1330 const std::string& macAddress, 1331 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1332 { 1333 setDbusProperty( 1334 asyncResp, "MACAddress", "xyz.openbmc_project.Network", 1335 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 1336 ifaceId, 1337 "xyz.openbmc_project.Network.MACAddress", "MACAddress", macAddress); 1338 } 1339 1340 inline void setDHCPEnabled(const std::string& ifaceId, 1341 const std::string& propertyName, const bool v4Value, 1342 const bool v6Value, 1343 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1344 { 1345 const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value); 1346 setDbusProperty( 1347 asyncResp, "DHCPv4", "xyz.openbmc_project.Network", 1348 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 1349 ifaceId, 1350 "xyz.openbmc_project.Network.EthernetInterface", propertyName, dhcp); 1351 } 1352 1353 enum class NetworkType 1354 { 1355 dhcp4, 1356 dhcp6 1357 }; 1358 1359 inline void setDHCPConfig(const std::string& propertyName, const bool& value, 1360 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1361 const std::string& ethifaceId, NetworkType type) 1362 { 1363 BMCWEB_LOG_DEBUG("{} = {}", propertyName, value); 1364 std::string redfishPropertyName; 1365 sdbusplus::message::object_path path("/xyz/openbmc_project/network/"); 1366 path /= ethifaceId; 1367 1368 if (type == NetworkType::dhcp4) 1369 { 1370 path /= "dhcp4"; 1371 redfishPropertyName = "DHCPv4"; 1372 } 1373 else 1374 { 1375 path /= "dhcp6"; 1376 redfishPropertyName = "DHCPv6"; 1377 } 1378 1379 setDbusProperty( 1380 asyncResp, redfishPropertyName, "xyz.openbmc_project.Network", path, 1381 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, value); 1382 } 1383 1384 inline void handleSLAACAutoConfigPatch( 1385 const std::string& ifaceId, bool ipv6AutoConfigEnabled, 1386 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1387 { 1388 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1389 path /= ifaceId; 1390 setDbusProperty(asyncResp, 1391 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", 1392 "xyz.openbmc_project.Network", path, 1393 "xyz.openbmc_project.Network.EthernetInterface", 1394 "IPv6AcceptRA", ipv6AutoConfigEnabled); 1395 } 1396 1397 inline void handleDHCPPatch(const std::string& ifaceId, 1398 const EthernetInterfaceData& ethData, 1399 const DHCPParameters& v4dhcpParms, 1400 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 = "IPv4StaticAddresses/" + 1538 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 = getNextStaticIpEntry(++nicIpEntry, 1547 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 = getNextStaticIpEntry(++nicIpEntry, 1684 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 = getNextStaticIpEntry(++nicIpEntry, 1701 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 = "IPv6StaticAddresses/" + 1743 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 = getNextStaticIpEntry(++nicIpEntry, 1792 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 = getNextStaticIpEntry(++nicIpEntry, 1824 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 1838 parseInterfaceData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1839 const std::string& ifaceId, 1840 const EthernetInterfaceData& ethData, 1841 const std::vector<IPv4AddressData>& ipv4Data, 1842 const std::vector<IPv6AddressData>& ipv6Data, 1843 const std::vector<StaticGatewayData>& ipv6GatewayData) 1844 { 1845 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1846 jsonResponse["Id"] = ifaceId; 1847 jsonResponse["@odata.id"] = 1848 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}", 1849 BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceId); 1850 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1851 1852 if (ethData.nicEnabled) 1853 { 1854 jsonResponse["LinkStatus"] = 1855 ethData.linkUp ? ethernet_interface::LinkStatus::LinkUp 1856 : ethernet_interface::LinkStatus::LinkDown; 1857 jsonResponse["Status"]["State"] = resource::State::Enabled; 1858 } 1859 else 1860 { 1861 jsonResponse["LinkStatus"] = ethernet_interface::LinkStatus::NoLink; 1862 jsonResponse["Status"]["State"] = resource::State::Disabled; 1863 } 1864 1865 jsonResponse["SpeedMbps"] = ethData.speed; 1866 jsonResponse["MTUSize"] = ethData.mtuSize; 1867 jsonResponse["MACAddress"] = ethData.macAddress; 1868 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1869 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1870 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled; 1871 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled; 1872 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.domainv4Enabled; 1873 jsonResponse["DHCPv6"]["OperatingMode"] = 1874 translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Enabled" 1875 : "Disabled"; 1876 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled; 1877 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled; 1878 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.domainv6Enabled; 1879 jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] = 1880 ethData.ipv6AcceptRa; 1881 1882 if (!ethData.hostName.empty()) 1883 { 1884 jsonResponse["HostName"] = ethData.hostName; 1885 1886 // When domain name is empty then it means, that it is a network 1887 // without domain names, and the host name itself must be treated as 1888 // FQDN 1889 std::string fqdn = ethData.hostName; 1890 if (!ethData.domainnames.empty()) 1891 { 1892 fqdn += "." + ethData.domainnames[0]; 1893 } 1894 jsonResponse["FQDN"] = fqdn; 1895 } 1896 1897 if (ethData.vlanId) 1898 { 1899 jsonResponse["EthernetInterfaceType"] = 1900 ethernet_interface::EthernetDeviceType::Virtual; 1901 jsonResponse["VLAN"]["VLANEnable"] = true; 1902 jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId; 1903 jsonResponse["VLAN"]["Tagged"] = true; 1904 1905 nlohmann::json::array_t relatedInterfaces; 1906 nlohmann::json& parentInterface = relatedInterfaces.emplace_back(); 1907 parentInterface["@odata.id"] = 1908 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces", 1909 BMCWEB_REDFISH_MANAGER_URI_NAME, 1910 extractParentInterfaceName(ifaceId)); 1911 jsonResponse["Links"]["RelatedInterfaces"] = 1912 std::move(relatedInterfaces); 1913 } 1914 else 1915 { 1916 jsonResponse["EthernetInterfaceType"] = 1917 ethernet_interface::EthernetDeviceType::Physical; 1918 } 1919 1920 jsonResponse["NameServers"] = ethData.nameServers; 1921 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1922 1923 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1924 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1925 ipv4Array = nlohmann::json::array(); 1926 ipv4StaticArray = nlohmann::json::array(); 1927 for (const auto& ipv4Config : ipv4Data) 1928 { 1929 std::string gatewayStr = ipv4Config.gateway; 1930 if (gatewayStr.empty()) 1931 { 1932 gatewayStr = "0.0.0.0"; 1933 } 1934 nlohmann::json::object_t ipv4; 1935 ipv4["AddressOrigin"] = ipv4Config.origin; 1936 ipv4["SubnetMask"] = ipv4Config.netmask; 1937 ipv4["Address"] = ipv4Config.address; 1938 ipv4["Gateway"] = gatewayStr; 1939 1940 if (ipv4Config.origin == "Static") 1941 { 1942 ipv4StaticArray.push_back(ipv4); 1943 } 1944 1945 ipv4Array.emplace_back(std::move(ipv4)); 1946 } 1947 1948 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; 1949 if (ipv6GatewayStr.empty()) 1950 { 1951 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1952 } 1953 1954 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1955 1956 nlohmann::json::array_t ipv6StaticGatewayArray; 1957 for (const auto& ipv6GatewayConfig : ipv6GatewayData) 1958 { 1959 nlohmann::json::object_t ipv6Gateway; 1960 ipv6Gateway["Address"] = ipv6GatewayConfig.gateway; 1961 ipv6Gateway["PrefixLength"] = ipv6GatewayConfig.prefixLength; 1962 ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway)); 1963 } 1964 jsonResponse["IPv6StaticDefaultGateways"] = 1965 std::move(ipv6StaticGatewayArray); 1966 1967 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 1968 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 1969 ipv6Array = nlohmann::json::array(); 1970 ipv6StaticArray = nlohmann::json::array(); 1971 nlohmann::json& ipv6AddrPolicyTable = 1972 jsonResponse["IPv6AddressPolicyTable"]; 1973 ipv6AddrPolicyTable = nlohmann::json::array(); 1974 for (const auto& ipv6Config : ipv6Data) 1975 { 1976 nlohmann::json::object_t ipv6; 1977 ipv6["Address"] = ipv6Config.address; 1978 ipv6["PrefixLength"] = ipv6Config.prefixLength; 1979 ipv6["AddressOrigin"] = ipv6Config.origin; 1980 1981 ipv6Array.emplace_back(std::move(ipv6)); 1982 if (ipv6Config.origin == "Static") 1983 { 1984 nlohmann::json::object_t ipv6Static; 1985 ipv6Static["Address"] = ipv6Config.address; 1986 ipv6Static["PrefixLength"] = ipv6Config.prefixLength; 1987 ipv6StaticArray.emplace_back(std::move(ipv6Static)); 1988 } 1989 } 1990 } 1991 1992 inline void afterDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1993 const std::string& ifaceId, 1994 const boost::system::error_code& ec, 1995 const sdbusplus::message_t& m) 1996 { 1997 if (!ec) 1998 { 1999 return; 2000 } 2001 const sd_bus_error* dbusError = m.get_error(); 2002 if (dbusError == nullptr) 2003 { 2004 messages::internalError(asyncResp->res); 2005 return; 2006 } 2007 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); 2008 2009 if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") == 2010 dbusError->name) 2011 { 2012 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2013 ifaceId); 2014 return; 2015 } 2016 if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") == 2017 dbusError->name) 2018 { 2019 messages::resourceCannotBeDeleted(asyncResp->res); 2020 return; 2021 } 2022 messages::internalError(asyncResp->res); 2023 } 2024 2025 inline void afterVlanCreate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2026 const std::string& parentInterfaceUri, 2027 const std::string& vlanInterface, 2028 const boost::system::error_code& ec, 2029 const sdbusplus::message_t& m 2030 2031 ) 2032 { 2033 if (ec) 2034 { 2035 const sd_bus_error* dbusError = m.get_error(); 2036 if (dbusError == nullptr) 2037 { 2038 messages::internalError(asyncResp->res); 2039 return; 2040 } 2041 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); 2042 2043 if (std::string_view( 2044 "xyz.openbmc_project.Common.Error.ResourceNotFound") == 2045 dbusError->name) 2046 { 2047 messages::propertyValueNotInList( 2048 asyncResp->res, parentInterfaceUri, 2049 "Links/RelatedInterfaces/0/@odata.id"); 2050 return; 2051 } 2052 if (std::string_view( 2053 "xyz.openbmc_project.Common.Error.InvalidArgument") == 2054 dbusError->name) 2055 { 2056 messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface", 2057 "Id", vlanInterface); 2058 return; 2059 } 2060 messages::internalError(asyncResp->res); 2061 return; 2062 } 2063 2064 const boost::urls::url vlanInterfaceUri = 2065 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}", 2066 BMCWEB_REDFISH_MANAGER_URI_NAME, vlanInterface); 2067 asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer()); 2068 } 2069 2070 inline void requestEthernetInterfacesRoutes(App& app) 2071 { 2072 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/") 2073 .privileges(redfish::privileges::getEthernetInterfaceCollection) 2074 .methods(boost::beast::http::verb::get)( 2075 [&app](const crow::Request& req, 2076 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2077 const std::string& managerId) { 2078 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2079 { 2080 return; 2081 } 2082 2083 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2084 { 2085 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2086 return; 2087 } 2088 2089 asyncResp->res.jsonValue["@odata.type"] = 2090 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 2091 asyncResp->res.jsonValue["@odata.id"] = 2092 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces", 2093 BMCWEB_REDFISH_MANAGER_URI_NAME); 2094 asyncResp->res.jsonValue["Name"] = 2095 "Ethernet Network Interface Collection"; 2096 asyncResp->res.jsonValue["Description"] = 2097 "Collection of EthernetInterfaces for this Manager"; 2098 2099 // Get eth interface list, and call the below callback for JSON 2100 // preparation 2101 getEthernetIfaceList( 2102 [asyncResp](const bool& success, 2103 const std::vector<std::string>& ifaceList) { 2104 if (!success) 2105 { 2106 messages::internalError(asyncResp->res); 2107 return; 2108 } 2109 2110 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 2111 ifaceArray = nlohmann::json::array(); 2112 for (const std::string& ifaceItem : ifaceList) 2113 { 2114 nlohmann::json::object_t iface; 2115 iface["@odata.id"] = boost::urls::format( 2116 "/redfish/v1/Managers/{}/EthernetInterfaces/{}", 2117 BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceItem); 2118 ifaceArray.push_back(std::move(iface)); 2119 } 2120 2121 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 2122 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2123 "/redfish/v1/Managers/{}/EthernetInterfaces", 2124 BMCWEB_REDFISH_MANAGER_URI_NAME); 2125 }); 2126 }); 2127 2128 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/") 2129 .privileges(redfish::privileges::postEthernetInterfaceCollection) 2130 .methods(boost::beast::http::verb::post)( 2131 [&app](const crow::Request& req, 2132 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2133 const std::string& managerId) { 2134 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2135 { 2136 return; 2137 } 2138 2139 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2140 { 2141 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2142 return; 2143 } 2144 2145 bool vlanEnable = false; 2146 uint32_t vlanId = 0; 2147 std::vector<nlohmann::json::object_t> relatedInterfaces; 2148 2149 if (!json_util::readJsonPatch(req, asyncResp->res, "VLAN/VLANEnable", 2150 vlanEnable, "VLAN/VLANId", vlanId, 2151 "Links/RelatedInterfaces", 2152 relatedInterfaces)) 2153 { 2154 return; 2155 } 2156 2157 if (relatedInterfaces.size() != 1) 2158 { 2159 messages::arraySizeTooLong(asyncResp->res, 2160 "Links/RelatedInterfaces", 2161 relatedInterfaces.size()); 2162 return; 2163 } 2164 2165 std::string parentInterfaceUri; 2166 if (!json_util::readJsonObject(relatedInterfaces[0], asyncResp->res, 2167 "@odata.id", parentInterfaceUri)) 2168 { 2169 messages::propertyMissing(asyncResp->res, 2170 "Links/RelatedInterfaces/0/@odata.id"); 2171 return; 2172 } 2173 BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri); 2174 2175 boost::system::result<boost::urls::url_view> parsedUri = 2176 boost::urls::parse_relative_ref(parentInterfaceUri); 2177 if (!parsedUri) 2178 { 2179 messages::propertyValueFormatError( 2180 asyncResp->res, parentInterfaceUri, 2181 "Links/RelatedInterfaces/0/@odata.id"); 2182 return; 2183 } 2184 2185 std::string parentInterface; 2186 if (!crow::utility::readUrlSegments( 2187 *parsedUri, "redfish", "v1", "Managers", "bmc", 2188 "EthernetInterfaces", std::ref(parentInterface))) 2189 { 2190 messages::propertyValueNotInList( 2191 asyncResp->res, parentInterfaceUri, 2192 "Links/RelatedInterfaces/0/@odata.id"); 2193 return; 2194 } 2195 2196 if (!vlanEnable) 2197 { 2198 // In OpenBMC implementation, VLANEnable cannot be false on 2199 // create 2200 messages::propertyValueIncorrect(asyncResp->res, "VLAN/VLANEnable", 2201 "false"); 2202 return; 2203 } 2204 2205 std::string vlanInterface = parentInterface + "_" + 2206 std::to_string(vlanId); 2207 crow::connections::systemBus->async_method_call( 2208 [asyncResp, parentInterfaceUri, 2209 vlanInterface](const boost::system::error_code& ec, 2210 const sdbusplus::message_t& m) { 2211 afterVlanCreate(asyncResp, parentInterfaceUri, vlanInterface, ec, 2212 m); 2213 }, 2214 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 2215 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", parentInterface, 2216 vlanId); 2217 }); 2218 2219 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/") 2220 .privileges(redfish::privileges::getEthernetInterface) 2221 .methods(boost::beast::http::verb::get)( 2222 [&app](const crow::Request& req, 2223 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2224 const std::string& managerId, const std::string& ifaceId) { 2225 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2226 { 2227 return; 2228 } 2229 2230 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2231 { 2232 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2233 return; 2234 } 2235 2236 getEthernetIfaceData( 2237 ifaceId, 2238 [asyncResp, 2239 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2240 const std::vector<IPv4AddressData>& ipv4Data, 2241 const std::vector<IPv6AddressData>& ipv6Data, 2242 const std::vector<StaticGatewayData>& ipv6GatewayData) { 2243 if (!success) 2244 { 2245 // TODO(Pawel)consider distinguish between non 2246 // existing object, and other errors 2247 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2248 ifaceId); 2249 return; 2250 } 2251 2252 asyncResp->res.jsonValue["@odata.type"] = 2253 "#EthernetInterface.v1_9_0.EthernetInterface"; 2254 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 2255 asyncResp->res.jsonValue["Description"] = 2256 "Management Network Interface"; 2257 2258 parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data, 2259 ipv6GatewayData); 2260 }); 2261 }); 2262 2263 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/") 2264 .privileges(redfish::privileges::patchEthernetInterface) 2265 .methods(boost::beast::http::verb::patch)( 2266 [&app](const crow::Request& req, 2267 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2268 const std::string& managerId, const std::string& ifaceId) { 2269 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2270 { 2271 return; 2272 } 2273 2274 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2275 { 2276 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2277 return; 2278 } 2279 2280 std::optional<std::string> hostname; 2281 std::optional<std::string> fqdn; 2282 std::optional<std::string> macAddress; 2283 std::optional<std::string> ipv6DefaultGateway; 2284 std::optional< 2285 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>> 2286 ipv4StaticAddresses; 2287 std::optional< 2288 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>> 2289 ipv6StaticAddresses; 2290 std::optional< 2291 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>> 2292 ipv6StaticDefaultGateways; 2293 std::optional<std::vector<std::string>> staticNameServers; 2294 std::optional<bool> ipv6AutoConfigEnabled; 2295 std::optional<bool> interfaceEnabled; 2296 std::optional<size_t> mtuSize; 2297 DHCPParameters v4dhcpParms; 2298 DHCPParameters v6dhcpParms; 2299 // clang-format off 2300 if (!json_util::readJsonPatch(req, asyncResp->res, 2301 "DHCPv4/DHCPEnabled", v4dhcpParms.dhcpv4Enabled, 2302 "DHCPv4/UseDNSServers", v4dhcpParms.useDnsServers, 2303 "DHCPv4/UseDomainName", v4dhcpParms.useDomainName, 2304 "DHCPv4/UseNTPServers", v4dhcpParms.useNtpServers, 2305 "DHCPv6/OperatingMode", v6dhcpParms.dhcpv6OperatingMode, 2306 "DHCPv6/UseDNSServers", v6dhcpParms.useDnsServers, 2307 "DHCPv6/UseDomainName", v6dhcpParms.useDomainName, 2308 "DHCPv6/UseNTPServers", v6dhcpParms.useNtpServers, 2309 "FQDN", fqdn, 2310 "HostName", hostname, 2311 "IPv4StaticAddresses", ipv4StaticAddresses, 2312 "IPv6DefaultGateway", ipv6DefaultGateway, 2313 "IPv6StaticAddresses", ipv6StaticAddresses, 2314 "IPv6StaticDefaultGateways", ipv6StaticDefaultGateways, 2315 "InterfaceEnabled", interfaceEnabled, 2316 "MACAddress", macAddress, 2317 "MTUSize", mtuSize, 2318 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", ipv6AutoConfigEnabled, 2319 "StaticNameServers", staticNameServers 2320 ) 2321 ) 2322 { 2323 return; 2324 } 2325 // clang-format on 2326 2327 // Get single eth interface data, and call the below callback 2328 // for JSON preparation 2329 getEthernetIfaceData( 2330 ifaceId, 2331 [asyncResp, ifaceId, hostname = std::move(hostname), 2332 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 2333 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 2334 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 2335 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 2336 ipv6StaticDefaultGateway = std::move(ipv6StaticDefaultGateways), 2337 staticNameServers = std::move(staticNameServers), mtuSize, 2338 ipv6AutoConfigEnabled, v4dhcpParms = std::move(v4dhcpParms), 2339 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 2340 const bool success, const EthernetInterfaceData& ethData, 2341 const std::vector<IPv4AddressData>& ipv4Data, 2342 const std::vector<IPv6AddressData>& ipv6Data, 2343 const std::vector<StaticGatewayData>& ipv6GatewayData) mutable { 2344 if (!success) 2345 { 2346 // ... otherwise return error 2347 // TODO(Pawel)consider distinguish between non 2348 // existing object, and other errors 2349 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2350 ifaceId); 2351 return; 2352 } 2353 2354 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, 2355 asyncResp); 2356 2357 if (hostname) 2358 { 2359 handleHostnamePatch(*hostname, asyncResp); 2360 } 2361 2362 if (ipv6AutoConfigEnabled) 2363 { 2364 handleSLAACAutoConfigPatch(ifaceId, *ipv6AutoConfigEnabled, 2365 asyncResp); 2366 } 2367 2368 if (fqdn) 2369 { 2370 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 2371 } 2372 2373 if (macAddress) 2374 { 2375 handleMACAddressPatch(ifaceId, *macAddress, asyncResp); 2376 } 2377 2378 if (ipv4StaticAddresses) 2379 { 2380 handleIPv4StaticPatch(ifaceId, *ipv4StaticAddresses, ethData, 2381 ipv4Data, asyncResp); 2382 } 2383 2384 if (staticNameServers) 2385 { 2386 handleStaticNameServersPatch(ifaceId, *staticNameServers, 2387 asyncResp); 2388 } 2389 2390 if (ipv6DefaultGateway) 2391 { 2392 messages::propertyNotWritable(asyncResp->res, 2393 "IPv6DefaultGateway"); 2394 } 2395 2396 if (ipv6StaticAddresses) 2397 { 2398 handleIPv6StaticAddressesPatch(ifaceId, *ipv6StaticAddresses, 2399 ipv6Data, asyncResp); 2400 } 2401 2402 if (ipv6StaticDefaultGateway) 2403 { 2404 handleIPv6DefaultGateway(ifaceId, *ipv6StaticDefaultGateway, 2405 ipv6GatewayData, asyncResp); 2406 } 2407 2408 if (interfaceEnabled) 2409 { 2410 setDbusProperty(asyncResp, "InterfaceEnabled", 2411 "xyz.openbmc_project.Network", 2412 sdbusplus::message::object_path( 2413 "/xyz/openbmc_project/network") / 2414 ifaceId, 2415 "xyz.openbmc_project.Network.EthernetInterface", 2416 "NICEnabled", *interfaceEnabled); 2417 } 2418 2419 if (mtuSize) 2420 { 2421 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 2422 } 2423 }); 2424 }); 2425 2426 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/") 2427 .privileges(redfish::privileges::deleteEthernetInterface) 2428 .methods(boost::beast::http::verb::delete_)( 2429 [&app](const crow::Request& req, 2430 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2431 const std::string& managerId, const std::string& ifaceId) { 2432 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2433 { 2434 return; 2435 } 2436 2437 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2438 { 2439 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2440 return; 2441 } 2442 2443 crow::connections::systemBus->async_method_call( 2444 [asyncResp, ifaceId](const boost::system::error_code& ec, 2445 const sdbusplus::message_t& m) { 2446 afterDelete(asyncResp, ifaceId, ec, m); 2447 }, 2448 "xyz.openbmc_project.Network", 2449 std::string("/xyz/openbmc_project/network/") + ifaceId, 2450 "xyz.openbmc_project.Object.Delete", "Delete"); 2451 }); 2452 } 2453 2454 } // namespace redfish 2455