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