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