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 setDbusProperty( 733 asyncResp, "xyz.openbmc_project.Network", 734 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 735 ifaceId, 736 "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway", 737 "Gateway", gateway); 738 } 739 /** 740 * @brief Creates a static IPv4 entry 741 * 742 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 743 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 744 * @param[in] gateway IPv4 address of this interfaces gateway 745 * @param[in] address IPv4 address to assign to this interface 746 * @param[io] asyncResp Response object that will be returned to client 747 * 748 * @return None 749 */ 750 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength, 751 const std::string& gateway, const std::string& address, 752 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 753 { 754 auto createIpHandler = [asyncResp, ifaceId, 755 gateway](const boost::system::error_code& ec) { 756 if (ec) 757 { 758 messages::internalError(asyncResp->res); 759 return; 760 } 761 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 762 }; 763 764 crow::connections::systemBus->async_method_call( 765 std::move(createIpHandler), "xyz.openbmc_project.Network", 766 "/xyz/openbmc_project/network/" + ifaceId, 767 "xyz.openbmc_project.Network.IP.Create", "IP", 768 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, 769 gateway); 770 } 771 772 /** 773 * @brief Deletes the IPv6 entry for this interface and creates a replacement 774 * static IPv6 entry 775 * 776 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 777 * @param[in] id The unique hash entry identifying the DBus entry 778 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 779 * @param[in] address IPv6 address to assign to this interface 780 * @param[io] asyncResp Response object that will be returned to client 781 * 782 * @return None 783 */ 784 785 enum class IpVersion 786 { 787 IpV4, 788 IpV6 789 }; 790 791 inline void deleteAndCreateIPAddress( 792 IpVersion version, const std::string& ifaceId, const std::string& id, 793 uint8_t prefixLength, const std::string& address, 794 const std::string& gateway, 795 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 796 { 797 crow::connections::systemBus->async_method_call( 798 [asyncResp, version, ifaceId, address, prefixLength, 799 gateway](const boost::system::error_code& ec) { 800 if (ec) 801 { 802 messages::internalError(asyncResp->res); 803 } 804 std::string protocol = "xyz.openbmc_project.Network.IP.Protocol."; 805 protocol += version == IpVersion::IpV4 ? "IPv4" : "IPv6"; 806 crow::connections::systemBus->async_method_call( 807 [asyncResp](const boost::system::error_code& ec2) { 808 if (ec2) 809 { 810 messages::internalError(asyncResp->res); 811 } 812 }, 813 "xyz.openbmc_project.Network", 814 "/xyz/openbmc_project/network/" + ifaceId, 815 "xyz.openbmc_project.Network.IP.Create", "IP", protocol, address, 816 prefixLength, gateway); 817 }, 818 "xyz.openbmc_project.Network", 819 "/xyz/openbmc_project/network/" + ifaceId + id, 820 "xyz.openbmc_project.Object.Delete", "Delete"); 821 } 822 823 inline bool extractIPv6DefaultGatewayData( 824 const std::string& ethifaceId, 825 const dbus::utility::ManagedObjectType& dbusData, 826 std::vector<StaticGatewayData>& staticGatewayConfig) 827 { 828 std::string staticGatewayPathStart("/xyz/openbmc_project/network/"); 829 staticGatewayPathStart += ethifaceId; 830 831 for (const auto& objpath : dbusData) 832 { 833 if (!std::string_view(objpath.first.str) 834 .starts_with(staticGatewayPathStart)) 835 { 836 continue; 837 } 838 for (const auto& interface : objpath.second) 839 { 840 if (interface.first != "xyz.openbmc_project.Network.StaticGateway") 841 { 842 continue; 843 } 844 StaticGatewayData& staticGateway = 845 staticGatewayConfig.emplace_back(); 846 staticGateway.id = objpath.first.filename(); 847 848 bool success = sdbusplus::unpackPropertiesNoThrow( 849 redfish::dbus_utils::UnpackErrorPrinter(), interface.second, 850 "Gateway", staticGateway.gateway, "PrefixLength", 851 staticGateway.prefixLength, "ProtocolType", 852 staticGateway.protocol); 853 if (!success) 854 { 855 return false; 856 } 857 } 858 } 859 return true; 860 } 861 862 /** 863 * @brief Creates IPv6 with given data 864 * 865 * @param[in] ifaceId Id of interface whose IP should be added 866 * @param[in] prefixLength Prefix length that needs to be added 867 * @param[in] address IP address that needs to be added 868 * @param[io] asyncResp Response object that will be returned to client 869 * 870 * @return None 871 */ 872 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength, 873 const std::string& address, 874 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 875 { 876 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 877 path /= ifaceId; 878 879 auto createIpHandler = [asyncResp, 880 address](const boost::system::error_code& ec) { 881 if (ec) 882 { 883 if (ec == boost::system::errc::io_error) 884 { 885 messages::propertyValueFormatError(asyncResp->res, address, 886 "Address"); 887 } 888 else 889 { 890 messages::internalError(asyncResp->res); 891 } 892 } 893 }; 894 // Passing null for gateway, as per redfish spec IPv6StaticAddresses 895 // object does not have associated gateway property 896 crow::connections::systemBus->async_method_call( 897 std::move(createIpHandler), "xyz.openbmc_project.Network", path, 898 "xyz.openbmc_project.Network.IP.Create", "IP", 899 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 900 ""); 901 } 902 903 /** 904 * @brief Deletes given IPv6 Static Gateway 905 * 906 * @param[in] ifaceId Id of interface whose IP should be deleted 907 * @param[in] ipHash DBus Hash id of IP that should be deleted 908 * @param[io] asyncResp Response object that will be returned to client 909 * 910 * @return None 911 */ 912 inline void 913 deleteIPv6Gateway(std::string_view gatewayId, 914 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 915 { 916 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 917 path /= gatewayId; 918 crow::connections::systemBus->async_method_call( 919 [asyncResp](const boost::system::error_code& ec) { 920 if (ec) 921 { 922 messages::internalError(asyncResp->res); 923 } 924 }, 925 "xyz.openbmc_project.Network", path, 926 "xyz.openbmc_project.Object.Delete", "Delete"); 927 } 928 929 /** 930 * @brief Creates IPv6 static default gateway with given data 931 * 932 * @param[in] ifaceId Id of interface whose IP should be added 933 * @param[in] prefixLength Prefix length that needs to be added 934 * @param[in] gateway Gateway address that needs to be added 935 * @param[io] asyncResp Response object that will be returned to client 936 * 937 * @return None 938 */ 939 inline void createIPv6DefaultGateway( 940 std::string_view ifaceId, size_t prefixLength, std::string_view gateway, 941 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 942 { 943 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 944 path /= ifaceId; 945 auto createIpHandler = [asyncResp](const boost::system::error_code& ec) { 946 if (ec) 947 { 948 messages::internalError(asyncResp->res); 949 } 950 }; 951 crow::connections::systemBus->async_method_call( 952 std::move(createIpHandler), "xyz.openbmc_project.Network", path, 953 "xyz.openbmc_project.Network.StaticGateway.Create", "StaticGateway", 954 gateway, prefixLength, "xyz.openbmc_project.Network.IP.Protocol.IPv6"); 955 } 956 957 /** 958 * @brief Deletes the IPv6 default gateway entry for this interface and 959 * creates a replacement IPv6 default gateway entry 960 * 961 * @param[in] ifaceId Id of interface upon which to create the IPv6 962 * entry 963 * @param[in] gateway IPv6 gateway to assign to this interface 964 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 965 * @param[io] asyncResp Response object that will be returned to client 966 * 967 * @return None 968 */ 969 inline void deleteAndCreateIPv6DefaultGateway( 970 std::string_view ifaceId, std::string_view gatewayId, 971 std::string_view gateway, size_t prefixLength, 972 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 973 { 974 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 975 path /= gatewayId; 976 crow::connections::systemBus->async_method_call( 977 [asyncResp, ifaceId, gateway, 978 prefixLength](const boost::system::error_code& ec) { 979 if (ec) 980 { 981 messages::internalError(asyncResp->res); 982 return; 983 } 984 createIPv6DefaultGateway(ifaceId, prefixLength, gateway, asyncResp); 985 }, 986 "xyz.openbmc_project.Network", path, 987 "xyz.openbmc_project.Object.Delete", "Delete"); 988 } 989 990 /** 991 * @brief Sets IPv6 default gateway with given data 992 * 993 * @param[in] ifaceId Id of interface whose gateway should be added 994 * @param[in] input Contains address that needs to be added 995 * @param[in] staticGatewayData Current static gateways in the system 996 * @param[io] asyncResp Response object that will be returned to client 997 * 998 * @return None 999 */ 1000 1001 inline void handleIPv6DefaultGateway( 1002 const std::string& ifaceId, const nlohmann::json::array_t& input, 1003 const std::vector<StaticGatewayData>& staticGatewayData, 1004 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1005 { 1006 size_t entryIdx = 1; 1007 std::vector<StaticGatewayData>::const_iterator staticGatewayEntry = 1008 staticGatewayData.begin(); 1009 1010 for (const nlohmann::json& thisJson : input) 1011 { 1012 // find the next gateway entry 1013 while (staticGatewayEntry != staticGatewayData.end()) 1014 { 1015 if (staticGatewayEntry->protocol == 1016 "xyz.openbmc_project.Network.IP.Protocol.IPv6") 1017 { 1018 break; 1019 } 1020 staticGatewayEntry++; 1021 } 1022 std::string pathString = "IPv6StaticDefaultGateways/" + 1023 std::to_string(entryIdx); 1024 if (thisJson.is_null()) 1025 { 1026 if (staticGatewayEntry == staticGatewayData.end()) 1027 { 1028 messages::resourceCannotBeDeleted(asyncResp->res); 1029 return; 1030 } 1031 deleteIPv6Gateway(staticGatewayEntry->id, asyncResp); 1032 return; 1033 } 1034 if (thisJson.is_object() && thisJson.empty()) 1035 { 1036 // Do nothing, but make sure the entry exists. 1037 if (staticGatewayEntry == staticGatewayData.end()) 1038 { 1039 messages::propertyValueFormatError(asyncResp->res, thisJson, 1040 pathString); 1041 return; 1042 } 1043 } 1044 std::optional<std::string> address; 1045 std::optional<size_t> prefixLength; 1046 1047 nlohmann::json thisJsonCopy = thisJson; 1048 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1049 address, "PrefixLength", prefixLength)) 1050 { 1051 return; 1052 } 1053 const std::string* addr = nullptr; 1054 size_t prefix = 0; 1055 if (address) 1056 { 1057 addr = &(*address); 1058 } 1059 else if (staticGatewayEntry != staticGatewayData.end()) 1060 { 1061 addr = &(staticGatewayEntry->gateway); 1062 } 1063 else 1064 { 1065 messages::propertyMissing(asyncResp->res, pathString + "/Address"); 1066 return; 1067 } 1068 if (prefixLength) 1069 { 1070 prefix = *prefixLength; 1071 } 1072 else if (staticGatewayEntry != staticGatewayData.end()) 1073 { 1074 prefix = staticGatewayEntry->prefixLength; 1075 } 1076 else 1077 { 1078 messages::propertyMissing(asyncResp->res, 1079 pathString + "/PrefixLength"); 1080 return; 1081 } 1082 if (staticGatewayEntry != staticGatewayData.end()) 1083 { 1084 deleteAndCreateIPv6DefaultGateway(ifaceId, staticGatewayEntry->id, 1085 *addr, prefix, asyncResp); 1086 staticGatewayEntry++; 1087 } 1088 else 1089 { 1090 createIPv6DefaultGateway(ifaceId, prefix, *addr, asyncResp); 1091 } 1092 entryIdx++; 1093 } 1094 } 1095 1096 /** 1097 * Function that retrieves all properties for given Ethernet Interface 1098 * Object 1099 * from EntityManager Network Manager 1100 * @param ethiface_id a eth interface id to query on DBus 1101 * @param callback a function that shall be called to convert Dbus output 1102 * into JSON 1103 */ 1104 template <typename CallbackFunc> 1105 void getEthernetIfaceData(const std::string& ethifaceId, 1106 CallbackFunc&& callback) 1107 { 1108 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1109 dbus::utility::getManagedObjects( 1110 "xyz.openbmc_project.Network", path, 1111 [ethifaceId{std::string{ethifaceId}}, 1112 callback{std::forward<CallbackFunc>(callback)}]( 1113 const boost::system::error_code& ec, 1114 const dbus::utility::ManagedObjectType& resp) { 1115 EthernetInterfaceData ethData{}; 1116 std::vector<IPv4AddressData> ipv4Data; 1117 std::vector<IPv6AddressData> ipv6Data; 1118 std::vector<StaticGatewayData> ipv6GatewayData; 1119 1120 if (ec) 1121 { 1122 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1123 return; 1124 } 1125 1126 bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData); 1127 if (!found) 1128 { 1129 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1130 return; 1131 } 1132 1133 extractIPData(ethifaceId, resp, ipv4Data); 1134 // Fix global GW 1135 for (IPv4AddressData& ipv4 : ipv4Data) 1136 { 1137 if (((ipv4.linktype == LinkType::Global) && 1138 (ipv4.gateway == "0.0.0.0")) || 1139 (ipv4.origin == "DHCP") || (ipv4.origin == "Static")) 1140 { 1141 ipv4.gateway = ethData.defaultGateway; 1142 } 1143 } 1144 1145 extractIPV6Data(ethifaceId, resp, ipv6Data); 1146 if (!extractIPv6DefaultGatewayData(ethifaceId, resp, ipv6GatewayData)) 1147 { 1148 callback(false, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1149 } 1150 // Finally make a callback with useful data 1151 callback(true, ethData, ipv4Data, ipv6Data, ipv6GatewayData); 1152 }); 1153 } 1154 1155 /** 1156 * Function that retrieves all Ethernet Interfaces available through Network 1157 * Manager 1158 * @param callback a function that shall be called to convert Dbus output 1159 * into JSON. 1160 */ 1161 template <typename CallbackFunc> 1162 void getEthernetIfaceList(CallbackFunc&& callback) 1163 { 1164 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1165 dbus::utility::getManagedObjects( 1166 "xyz.openbmc_project.Network", path, 1167 [callback{std::forward<CallbackFunc>(callback)}]( 1168 const boost::system::error_code& ec, 1169 const dbus::utility::ManagedObjectType& resp) { 1170 // Callback requires vector<string> to retrieve all available 1171 // ethernet interfaces 1172 std::vector<std::string> ifaceList; 1173 ifaceList.reserve(resp.size()); 1174 if (ec) 1175 { 1176 callback(false, ifaceList); 1177 return; 1178 } 1179 1180 // Iterate over all retrieved ObjectPaths. 1181 for (const auto& objpath : resp) 1182 { 1183 // And all interfaces available for certain ObjectPath. 1184 for (const auto& interface : objpath.second) 1185 { 1186 // If interface is 1187 // xyz.openbmc_project.Network.EthernetInterface, this is 1188 // what we're looking for. 1189 if (interface.first == 1190 "xyz.openbmc_project.Network.EthernetInterface") 1191 { 1192 std::string ifaceId = objpath.first.filename(); 1193 if (ifaceId.empty()) 1194 { 1195 continue; 1196 } 1197 // and put it into output vector. 1198 ifaceList.emplace_back(ifaceId); 1199 } 1200 } 1201 } 1202 1203 std::ranges::sort(ifaceList, AlphanumLess<std::string>()); 1204 1205 // Finally make a callback with useful data 1206 callback(true, ifaceList); 1207 }); 1208 } 1209 1210 inline void 1211 handleHostnamePatch(const std::string& hostname, 1212 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1213 { 1214 // SHOULD handle host names of up to 255 characters(RFC 1123) 1215 if (hostname.length() > 255) 1216 { 1217 messages::propertyValueFormatError(asyncResp->res, hostname, 1218 "HostName"); 1219 return; 1220 } 1221 setDbusProperty( 1222 asyncResp, "xyz.openbmc_project.Network", 1223 sdbusplus::message::object_path("/xyz/openbmc_project/network/config"), 1224 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 1225 "HostName", hostname); 1226 } 1227 1228 inline void 1229 handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize, 1230 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1231 { 1232 sdbusplus::message::object_path objPath("/xyz/openbmc_project/network"); 1233 objPath /= ifaceId; 1234 setDbusProperty(asyncResp, "xyz.openbmc_project.Network", objPath, 1235 "xyz.openbmc_project.Network.EthernetInterface", "MTU", 1236 "MTUSize", mtuSize); 1237 } 1238 1239 inline void 1240 handleDomainnamePatch(const std::string& ifaceId, 1241 const std::string& domainname, 1242 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1243 { 1244 std::vector<std::string> vectorDomainname = {domainname}; 1245 setDbusProperty( 1246 asyncResp, "xyz.openbmc_project.Network", 1247 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 1248 ifaceId, 1249 "xyz.openbmc_project.Network.EthernetInterface", "DomainName", "FQDN", 1250 vectorDomainname); 1251 } 1252 1253 inline bool isHostnameValid(const std::string& hostname) 1254 { 1255 // A valid host name can never have the dotted-decimal form (RFC 1123) 1256 if (std::ranges::all_of(hostname, ::isdigit)) 1257 { 1258 return false; 1259 } 1260 // Each label(hostname/subdomains) within a valid FQDN 1261 // MUST handle host names of up to 63 characters (RFC 1123) 1262 // labels cannot start or end with hyphens (RFC 952) 1263 // labels can start with numbers (RFC 1123) 1264 const static std::regex pattern( 1265 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 1266 1267 return std::regex_match(hostname, pattern); 1268 } 1269 1270 inline bool isDomainnameValid(const std::string& domainname) 1271 { 1272 // Can have multiple subdomains 1273 // Top Level Domain's min length is 2 character 1274 const static std::regex pattern( 1275 "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$"); 1276 1277 return std::regex_match(domainname, pattern); 1278 } 1279 1280 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn, 1281 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1282 { 1283 // Total length of FQDN must not exceed 255 characters(RFC 1035) 1284 if (fqdn.length() > 255) 1285 { 1286 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1287 return; 1288 } 1289 1290 size_t pos = fqdn.find('.'); 1291 if (pos == std::string::npos) 1292 { 1293 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1294 return; 1295 } 1296 1297 std::string hostname; 1298 std::string domainname; 1299 domainname = (fqdn).substr(pos + 1); 1300 hostname = (fqdn).substr(0, pos); 1301 1302 if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) 1303 { 1304 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1305 return; 1306 } 1307 1308 handleHostnamePatch(hostname, asyncResp); 1309 handleDomainnamePatch(ifaceId, domainname, asyncResp); 1310 } 1311 1312 inline void 1313 handleMACAddressPatch(const std::string& ifaceId, 1314 const std::string& macAddress, 1315 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1316 { 1317 setDbusProperty( 1318 asyncResp, "xyz.openbmc_project.Network", 1319 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 1320 ifaceId, 1321 "xyz.openbmc_project.Network.MACAddress", "MACAddress", "MACAddress", 1322 macAddress); 1323 } 1324 1325 inline void setDHCPEnabled(const std::string& ifaceId, 1326 const std::string& propertyName, const bool v4Value, 1327 const bool v6Value, 1328 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1329 { 1330 const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value); 1331 setDbusProperty( 1332 asyncResp, "xyz.openbmc_project.Network", 1333 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 1334 ifaceId, 1335 "xyz.openbmc_project.Network.EthernetInterface", propertyName, "DHCPv4", 1336 dhcp); 1337 } 1338 1339 enum class NetworkType 1340 { 1341 dhcp4, 1342 dhcp6 1343 }; 1344 1345 inline void setDHCPConfig(const std::string& propertyName, const bool& value, 1346 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1347 const std::string& ethifaceId, NetworkType type) 1348 { 1349 BMCWEB_LOG_DEBUG("{} = {}", propertyName, value); 1350 std::string redfishPropertyName; 1351 sdbusplus::message::object_path path("/xyz/openbmc_project/network/"); 1352 path /= ethifaceId; 1353 1354 if (type == NetworkType::dhcp4) 1355 { 1356 path /= "dhcp4"; 1357 redfishPropertyName = "DHCPv4"; 1358 } 1359 else 1360 { 1361 path /= "dhcp6"; 1362 redfishPropertyName = "DHCPv6"; 1363 } 1364 1365 setDbusProperty(asyncResp, "xyz.openbmc_project.Network", path, 1366 "xyz.openbmc_project.Network.DHCPConfiguration", 1367 propertyName, redfishPropertyName, value); 1368 } 1369 1370 inline void handleSLAACAutoConfigPatch( 1371 const std::string& ifaceId, bool ipv6AutoConfigEnabled, 1372 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1373 { 1374 sdbusplus::message::object_path path("/xyz/openbmc_project/network"); 1375 path /= ifaceId; 1376 setDbusProperty(asyncResp, "xyz.openbmc_project.Network", path, 1377 "xyz.openbmc_project.Network.EthernetInterface", 1378 "IPv6AcceptRA", 1379 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", 1380 ipv6AutoConfigEnabled); 1381 } 1382 1383 inline void handleDHCPPatch(const std::string& ifaceId, 1384 const EthernetInterfaceData& ethData, 1385 const DHCPParameters& v4dhcpParms, 1386 const DHCPParameters& v6dhcpParms, 1387 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1388 { 1389 bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1390 bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false); 1391 1392 bool nextv4DHCPState = 1393 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1394 1395 bool nextv6DHCPState{}; 1396 if (v6dhcpParms.dhcpv6OperatingMode) 1397 { 1398 if ((*v6dhcpParms.dhcpv6OperatingMode != "Enabled") && 1399 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1400 { 1401 messages::propertyValueFormatError(asyncResp->res, 1402 *v6dhcpParms.dhcpv6OperatingMode, 1403 "OperatingMode"); 1404 return; 1405 } 1406 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Enabled"); 1407 } 1408 else 1409 { 1410 nextv6DHCPState = ipv6Active; 1411 } 1412 1413 bool nextDNSv4 = ethData.dnsv4Enabled; 1414 bool nextDNSv6 = ethData.dnsv6Enabled; 1415 if (v4dhcpParms.useDnsServers) 1416 { 1417 nextDNSv4 = *v4dhcpParms.useDnsServers; 1418 } 1419 if (v6dhcpParms.useDnsServers) 1420 { 1421 nextDNSv6 = *v6dhcpParms.useDnsServers; 1422 } 1423 1424 bool nextNTPv4 = ethData.ntpv4Enabled; 1425 bool nextNTPv6 = ethData.ntpv6Enabled; 1426 if (v4dhcpParms.useNtpServers) 1427 { 1428 nextNTPv4 = *v4dhcpParms.useNtpServers; 1429 } 1430 if (v6dhcpParms.useNtpServers) 1431 { 1432 nextNTPv6 = *v6dhcpParms.useNtpServers; 1433 } 1434 1435 bool nextUsev4Domain = ethData.domainv4Enabled; 1436 bool nextUsev6Domain = ethData.domainv6Enabled; 1437 if (v4dhcpParms.useDomainName) 1438 { 1439 nextUsev4Domain = *v4dhcpParms.useDomainName; 1440 } 1441 if (v6dhcpParms.useDomainName) 1442 { 1443 nextUsev6Domain = *v6dhcpParms.useDomainName; 1444 } 1445 1446 BMCWEB_LOG_DEBUG("set DHCPEnabled..."); 1447 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1448 asyncResp); 1449 BMCWEB_LOG_DEBUG("set DNSEnabled..."); 1450 setDHCPConfig("DNSEnabled", nextDNSv4, asyncResp, ifaceId, 1451 NetworkType::dhcp4); 1452 BMCWEB_LOG_DEBUG("set NTPEnabled..."); 1453 setDHCPConfig("NTPEnabled", nextNTPv4, asyncResp, ifaceId, 1454 NetworkType::dhcp4); 1455 BMCWEB_LOG_DEBUG("set DomainEnabled..."); 1456 setDHCPConfig("DomainEnabled", nextUsev4Domain, asyncResp, ifaceId, 1457 NetworkType::dhcp4); 1458 BMCWEB_LOG_DEBUG("set DNSEnabled for dhcp6..."); 1459 setDHCPConfig("DNSEnabled", nextDNSv6, asyncResp, ifaceId, 1460 NetworkType::dhcp6); 1461 BMCWEB_LOG_DEBUG("set NTPEnabled for dhcp6..."); 1462 setDHCPConfig("NTPEnabled", nextNTPv6, asyncResp, ifaceId, 1463 NetworkType::dhcp6); 1464 BMCWEB_LOG_DEBUG("set DomainEnabled for dhcp6..."); 1465 setDHCPConfig("DomainEnabled", nextUsev6Domain, asyncResp, ifaceId, 1466 NetworkType::dhcp6); 1467 } 1468 1469 inline std::vector<IPv4AddressData>::const_iterator getNextStaticIpEntry( 1470 const std::vector<IPv4AddressData>::const_iterator& head, 1471 const std::vector<IPv4AddressData>::const_iterator& end) 1472 { 1473 return std::find_if(head, end, [](const IPv4AddressData& value) { 1474 return value.origin == "Static"; 1475 }); 1476 } 1477 1478 inline std::vector<IPv6AddressData>::const_iterator getNextStaticIpEntry( 1479 const std::vector<IPv6AddressData>::const_iterator& head, 1480 const std::vector<IPv6AddressData>::const_iterator& end) 1481 { 1482 return std::find_if(head, end, [](const IPv6AddressData& value) { 1483 return value.origin == "Static"; 1484 }); 1485 } 1486 1487 inline void 1488 handleIPv4StaticPatch(const std::string& ifaceId, 1489 nlohmann::json::array_t& input, 1490 const std::vector<IPv4AddressData>& ipv4Data, 1491 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1492 { 1493 if (input.empty()) 1494 { 1495 messages::propertyValueTypeError(asyncResp->res, input, 1496 "IPv4StaticAddresses"); 1497 return; 1498 } 1499 1500 unsigned entryIdx = 1; 1501 // Find the first static IP address currently active on the NIC and 1502 // match it to the first JSON element in the IPv4StaticAddresses array. 1503 // Match each subsequent JSON element to the next static IP programmed 1504 // into the NIC. 1505 std::vector<IPv4AddressData>::const_iterator nicIpEntry = 1506 getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1507 1508 for (nlohmann::json& thisJson : input) 1509 { 1510 std::string pathString = "IPv4StaticAddresses/" + 1511 std::to_string(entryIdx); 1512 1513 if (!thisJson.is_null() && !thisJson.empty()) 1514 { 1515 std::optional<std::string> address; 1516 std::optional<std::string> subnetMask; 1517 std::optional<std::string> gateway; 1518 1519 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1520 address, "SubnetMask", subnetMask, 1521 "Gateway", gateway)) 1522 { 1523 messages::propertyValueFormatError(asyncResp->res, thisJson, 1524 pathString); 1525 return; 1526 } 1527 1528 // Find the address/subnet/gateway values. Any values that are 1529 // not explicitly provided are assumed to be unmodified from the 1530 // current state of the interface. Merge existing state into the 1531 // current request. 1532 if (address) 1533 { 1534 if (!ip_util::ipv4VerifyIpAndGetBitcount(*address)) 1535 { 1536 messages::propertyValueFormatError(asyncResp->res, *address, 1537 pathString + "/Address"); 1538 return; 1539 } 1540 } 1541 else if (nicIpEntry != ipv4Data.cend()) 1542 { 1543 address = (nicIpEntry->address); 1544 } 1545 else 1546 { 1547 messages::propertyMissing(asyncResp->res, 1548 pathString + "/Address"); 1549 return; 1550 } 1551 1552 uint8_t prefixLength = 0; 1553 if (subnetMask) 1554 { 1555 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask, 1556 &prefixLength)) 1557 { 1558 messages::propertyValueFormatError( 1559 asyncResp->res, *subnetMask, 1560 pathString + "/SubnetMask"); 1561 return; 1562 } 1563 } 1564 else if (nicIpEntry != ipv4Data.cend()) 1565 { 1566 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, 1567 &prefixLength)) 1568 { 1569 messages::propertyValueFormatError( 1570 asyncResp->res, nicIpEntry->netmask, 1571 pathString + "/SubnetMask"); 1572 return; 1573 } 1574 } 1575 else 1576 { 1577 messages::propertyMissing(asyncResp->res, 1578 pathString + "/SubnetMask"); 1579 return; 1580 } 1581 1582 if (gateway) 1583 { 1584 if (!ip_util::ipv4VerifyIpAndGetBitcount(*gateway)) 1585 { 1586 messages::propertyValueFormatError(asyncResp->res, *gateway, 1587 pathString + "/Gateway"); 1588 return; 1589 } 1590 } 1591 else if (nicIpEntry != ipv4Data.cend()) 1592 { 1593 gateway = nicIpEntry->gateway; 1594 } 1595 else 1596 { 1597 messages::propertyMissing(asyncResp->res, 1598 pathString + "/Gateway"); 1599 return; 1600 } 1601 1602 if (nicIpEntry != ipv4Data.cend()) 1603 { 1604 deleteAndCreateIPAddress(IpVersion::IpV4, ifaceId, 1605 nicIpEntry->id, prefixLength, *address, 1606 *gateway, asyncResp); 1607 nicIpEntry = getNextStaticIpEntry(++nicIpEntry, 1608 ipv4Data.cend()); 1609 } 1610 else 1611 { 1612 createIPv4(ifaceId, prefixLength, *gateway, *address, 1613 asyncResp); 1614 } 1615 entryIdx++; 1616 } 1617 else 1618 { 1619 if (nicIpEntry == ipv4Data.cend()) 1620 { 1621 // Requesting a DELETE/DO NOT MODIFY action for an item 1622 // that isn't present on the eth(n) interface. Input JSON is 1623 // in error, so bail out. 1624 if (thisJson.is_null()) 1625 { 1626 messages::resourceCannotBeDeleted(asyncResp->res); 1627 return; 1628 } 1629 messages::propertyValueFormatError(asyncResp->res, thisJson, 1630 pathString); 1631 return; 1632 } 1633 1634 if (thisJson.is_null()) 1635 { 1636 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp); 1637 } 1638 if (nicIpEntry != ipv4Data.cend()) 1639 { 1640 nicIpEntry = getNextStaticIpEntry(++nicIpEntry, 1641 ipv4Data.cend()); 1642 } 1643 entryIdx++; 1644 } 1645 } 1646 } 1647 1648 inline void handleStaticNameServersPatch( 1649 const std::string& ifaceId, 1650 const std::vector<std::string>& updatedStaticNameServers, 1651 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1652 { 1653 setDbusProperty( 1654 asyncResp, "xyz.openbmc_project.Network", 1655 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 1656 ifaceId, 1657 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", 1658 "StaticNameServers", updatedStaticNameServers); 1659 } 1660 1661 inline void handleIPv6StaticAddressesPatch( 1662 const std::string& ifaceId, const nlohmann::json::array_t& input, 1663 const std::vector<IPv6AddressData>& ipv6Data, 1664 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1665 { 1666 if (input.empty()) 1667 { 1668 messages::propertyValueTypeError(asyncResp->res, input, 1669 "IPv6StaticAddresses"); 1670 return; 1671 } 1672 size_t entryIdx = 1; 1673 std::vector<IPv6AddressData>::const_iterator nicIpEntry = 1674 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1675 for (const nlohmann::json& thisJson : input) 1676 { 1677 std::string pathString = "IPv6StaticAddresses/" + 1678 std::to_string(entryIdx); 1679 1680 if (!thisJson.is_null() && !thisJson.empty()) 1681 { 1682 std::optional<std::string> address; 1683 std::optional<uint8_t> prefixLength; 1684 nlohmann::json thisJsonCopy = thisJson; 1685 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1686 address, "PrefixLength", prefixLength)) 1687 { 1688 messages::propertyValueFormatError(asyncResp->res, thisJson, 1689 pathString); 1690 return; 1691 } 1692 1693 // Find the address and prefixLength values. Any values that are 1694 // not explicitly provided are assumed to be unmodified from the 1695 // current state of the interface. Merge existing state into the 1696 // current request. 1697 if (!address) 1698 { 1699 if (nicIpEntry == ipv6Data.end()) 1700 { 1701 messages::propertyMissing(asyncResp->res, 1702 pathString + "/Address"); 1703 return; 1704 } 1705 address = nicIpEntry->address; 1706 } 1707 1708 if (!prefixLength) 1709 { 1710 if (nicIpEntry == ipv6Data.end()) 1711 { 1712 messages::propertyMissing(asyncResp->res, 1713 pathString + "/PrefixLength"); 1714 return; 1715 } 1716 prefixLength = nicIpEntry->prefixLength; 1717 } 1718 1719 if (nicIpEntry != ipv6Data.end()) 1720 { 1721 deleteAndCreateIPAddress(IpVersion::IpV6, ifaceId, 1722 nicIpEntry->id, *prefixLength, 1723 *address, "", asyncResp); 1724 nicIpEntry = getNextStaticIpEntry(++nicIpEntry, 1725 ipv6Data.cend()); 1726 } 1727 else 1728 { 1729 createIPv6(ifaceId, *prefixLength, *address, asyncResp); 1730 } 1731 entryIdx++; 1732 } 1733 else 1734 { 1735 if (nicIpEntry == ipv6Data.end()) 1736 { 1737 // Requesting a DELETE/DO NOT MODIFY action for an item 1738 // that isn't present on the eth(n) interface. Input JSON is 1739 // in error, so bail out. 1740 if (thisJson.is_null()) 1741 { 1742 messages::resourceCannotBeDeleted(asyncResp->res); 1743 return; 1744 } 1745 messages::propertyValueFormatError(asyncResp->res, thisJson, 1746 pathString); 1747 return; 1748 } 1749 1750 if (thisJson.is_null()) 1751 { 1752 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp); 1753 } 1754 if (nicIpEntry != ipv6Data.cend()) 1755 { 1756 nicIpEntry = getNextStaticIpEntry(++nicIpEntry, 1757 ipv6Data.cend()); 1758 } 1759 entryIdx++; 1760 } 1761 } 1762 } 1763 1764 inline std::string extractParentInterfaceName(const std::string& ifaceId) 1765 { 1766 std::size_t pos = ifaceId.find('_'); 1767 return ifaceId.substr(0, pos); 1768 } 1769 1770 inline void 1771 parseInterfaceData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1772 const std::string& ifaceId, 1773 const EthernetInterfaceData& ethData, 1774 const std::vector<IPv4AddressData>& ipv4Data, 1775 const std::vector<IPv6AddressData>& ipv6Data, 1776 const std::vector<StaticGatewayData>& ipv6GatewayData) 1777 { 1778 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1779 jsonResponse["Id"] = ifaceId; 1780 jsonResponse["@odata.id"] = boost::urls::format( 1781 "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", ifaceId); 1782 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1783 1784 if constexpr (bmcwebEnableHealthPopulate) 1785 { 1786 constexpr std::array<std::string_view, 1> inventoryForEthernet = { 1787 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1788 auto health = std::make_shared<HealthPopulate>(asyncResp); 1789 dbus::utility::getSubTreePaths( 1790 "/", 0, inventoryForEthernet, 1791 [health](const boost::system::error_code& ec, 1792 const dbus::utility::MapperGetSubTreePathsResponse& resp) { 1793 if (ec) 1794 { 1795 return; 1796 } 1797 1798 health->inventory = resp; 1799 }); 1800 1801 health->populate(); 1802 } 1803 1804 if (ethData.nicEnabled) 1805 { 1806 jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1807 jsonResponse["Status"]["State"] = "Enabled"; 1808 } 1809 else 1810 { 1811 jsonResponse["LinkStatus"] = "NoLink"; 1812 jsonResponse["Status"]["State"] = "Disabled"; 1813 } 1814 1815 jsonResponse["SpeedMbps"] = ethData.speed; 1816 jsonResponse["MTUSize"] = ethData.mtuSize; 1817 jsonResponse["MACAddress"] = ethData.macAddress; 1818 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1819 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1820 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled; 1821 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled; 1822 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNamev4Enabled; 1823 jsonResponse["DHCPv6"]["OperatingMode"] = 1824 translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Enabled" 1825 : "Disabled"; 1826 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled; 1827 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled; 1828 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNamev6Enabled; 1829 jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] = 1830 ethData.ipv6AcceptRa; 1831 1832 if (!ethData.hostName.empty()) 1833 { 1834 jsonResponse["HostName"] = ethData.hostName; 1835 1836 // When domain name is empty then it means, that it is a network 1837 // without domain names, and the host name itself must be treated as 1838 // FQDN 1839 std::string fqdn = ethData.hostName; 1840 if (!ethData.domainnames.empty()) 1841 { 1842 fqdn += "." + ethData.domainnames[0]; 1843 } 1844 jsonResponse["FQDN"] = fqdn; 1845 } 1846 1847 if (ethData.vlanId) 1848 { 1849 jsonResponse["EthernetInterfaceType"] = "Virtual"; 1850 jsonResponse["VLAN"]["VLANEnable"] = true; 1851 jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId; 1852 jsonResponse["VLAN"]["Tagged"] = true; 1853 1854 nlohmann::json::array_t relatedInterfaces; 1855 nlohmann::json& parentInterface = relatedInterfaces.emplace_back(); 1856 parentInterface["@odata.id"] = 1857 boost::urls::format("/redfish/v1/Managers/bmc/EthernetInterfaces", 1858 extractParentInterfaceName(ifaceId)); 1859 jsonResponse["Links"]["RelatedInterfaces"] = 1860 std::move(relatedInterfaces); 1861 } 1862 else 1863 { 1864 jsonResponse["EthernetInterfaceType"] = "Physical"; 1865 } 1866 1867 jsonResponse["NameServers"] = ethData.nameServers; 1868 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1869 1870 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1871 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1872 ipv4Array = nlohmann::json::array(); 1873 ipv4StaticArray = nlohmann::json::array(); 1874 for (const auto& ipv4Config : ipv4Data) 1875 { 1876 std::string gatewayStr = ipv4Config.gateway; 1877 if (gatewayStr.empty()) 1878 { 1879 gatewayStr = "0.0.0.0"; 1880 } 1881 nlohmann::json::object_t ipv4; 1882 ipv4["AddressOrigin"] = ipv4Config.origin; 1883 ipv4["SubnetMask"] = ipv4Config.netmask; 1884 ipv4["Address"] = ipv4Config.address; 1885 ipv4["Gateway"] = gatewayStr; 1886 1887 if (ipv4Config.origin == "Static") 1888 { 1889 ipv4StaticArray.push_back(ipv4); 1890 } 1891 1892 ipv4Array.emplace_back(std::move(ipv4)); 1893 } 1894 1895 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; 1896 if (ipv6GatewayStr.empty()) 1897 { 1898 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1899 } 1900 1901 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1902 1903 nlohmann::json::array_t ipv6StaticGatewayArray; 1904 for (const auto& ipv6GatewayConfig : ipv6GatewayData) 1905 { 1906 nlohmann::json::object_t ipv6Gateway; 1907 ipv6Gateway["Address"] = ipv6GatewayConfig.gateway; 1908 ipv6Gateway["PrefixLength"] = ipv6GatewayConfig.prefixLength; 1909 ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway)); 1910 } 1911 jsonResponse["IPv6StaticDefaultGateways"] = 1912 std::move(ipv6StaticGatewayArray); 1913 1914 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 1915 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 1916 ipv6Array = nlohmann::json::array(); 1917 ipv6StaticArray = nlohmann::json::array(); 1918 nlohmann::json& ipv6AddrPolicyTable = 1919 jsonResponse["IPv6AddressPolicyTable"]; 1920 ipv6AddrPolicyTable = nlohmann::json::array(); 1921 for (const auto& ipv6Config : ipv6Data) 1922 { 1923 nlohmann::json::object_t ipv6; 1924 ipv6["Address"] = ipv6Config.address; 1925 ipv6["PrefixLength"] = ipv6Config.prefixLength; 1926 ipv6["AddressOrigin"] = ipv6Config.origin; 1927 1928 ipv6Array.emplace_back(std::move(ipv6)); 1929 if (ipv6Config.origin == "Static") 1930 { 1931 nlohmann::json::object_t ipv6Static; 1932 ipv6Static["Address"] = ipv6Config.address; 1933 ipv6Static["PrefixLength"] = ipv6Config.prefixLength; 1934 ipv6StaticArray.emplace_back(std::move(ipv6Static)); 1935 } 1936 } 1937 } 1938 1939 inline void afterDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1940 const std::string& ifaceId, 1941 const boost::system::error_code& ec, 1942 const sdbusplus::message_t& m) 1943 { 1944 if (!ec) 1945 { 1946 return; 1947 } 1948 const sd_bus_error* dbusError = m.get_error(); 1949 if (dbusError == nullptr) 1950 { 1951 messages::internalError(asyncResp->res); 1952 return; 1953 } 1954 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); 1955 1956 if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") == 1957 dbusError->name) 1958 { 1959 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1960 ifaceId); 1961 return; 1962 } 1963 if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") == 1964 dbusError->name) 1965 { 1966 messages::resourceCannotBeDeleted(asyncResp->res); 1967 return; 1968 } 1969 messages::internalError(asyncResp->res); 1970 } 1971 1972 inline void afterVlanCreate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1973 const std::string& parentInterfaceUri, 1974 const std::string& vlanInterface, 1975 const boost::system::error_code& ec, 1976 const sdbusplus::message_t& m 1977 1978 ) 1979 { 1980 if (ec) 1981 { 1982 const sd_bus_error* dbusError = m.get_error(); 1983 if (dbusError == nullptr) 1984 { 1985 messages::internalError(asyncResp->res); 1986 return; 1987 } 1988 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); 1989 1990 if (std::string_view( 1991 "xyz.openbmc_project.Common.Error.ResourceNotFound") == 1992 dbusError->name) 1993 { 1994 messages::propertyValueNotInList( 1995 asyncResp->res, parentInterfaceUri, 1996 "Links/RelatedInterfaces/0/@odata.id"); 1997 return; 1998 } 1999 if (std::string_view( 2000 "xyz.openbmc_project.Common.Error.InvalidArgument") == 2001 dbusError->name) 2002 { 2003 messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface", 2004 "Id", vlanInterface); 2005 return; 2006 } 2007 messages::internalError(asyncResp->res); 2008 return; 2009 } 2010 2011 const boost::urls::url vlanInterfaceUri = boost::urls::format( 2012 "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", vlanInterface); 2013 asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer()); 2014 } 2015 2016 inline void requestEthernetInterfacesRoutes(App& app) 2017 { 2018 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 2019 .privileges(redfish::privileges::getEthernetInterfaceCollection) 2020 .methods(boost::beast::http::verb::get)( 2021 [&app](const crow::Request& req, 2022 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2023 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2024 { 2025 return; 2026 } 2027 2028 asyncResp->res.jsonValue["@odata.type"] = 2029 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 2030 asyncResp->res.jsonValue["@odata.id"] = 2031 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 2032 asyncResp->res.jsonValue["Name"] = 2033 "Ethernet Network Interface Collection"; 2034 asyncResp->res.jsonValue["Description"] = 2035 "Collection of EthernetInterfaces for this Manager"; 2036 2037 // Get eth interface list, and call the below callback for JSON 2038 // preparation 2039 getEthernetIfaceList( 2040 [asyncResp](const bool& success, 2041 const std::vector<std::string>& ifaceList) { 2042 if (!success) 2043 { 2044 messages::internalError(asyncResp->res); 2045 return; 2046 } 2047 2048 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 2049 ifaceArray = nlohmann::json::array(); 2050 for (const std::string& ifaceItem : ifaceList) 2051 { 2052 nlohmann::json::object_t iface; 2053 iface["@odata.id"] = boost::urls::format( 2054 "/redfish/v1/Managers/bmc/EthernetInterfaces/{}", 2055 ifaceItem); 2056 ifaceArray.push_back(std::move(iface)); 2057 } 2058 2059 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 2060 asyncResp->res.jsonValue["@odata.id"] = 2061 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 2062 }); 2063 }); 2064 2065 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 2066 .privileges(redfish::privileges::postEthernetInterfaceCollection) 2067 .methods(boost::beast::http::verb::post)( 2068 [&app](const crow::Request& req, 2069 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2070 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2071 { 2072 return; 2073 } 2074 2075 bool vlanEnable = false; 2076 uint32_t vlanId = 0; 2077 nlohmann::json::array_t relatedInterfaces; 2078 2079 if (!json_util::readJsonPatch(req, asyncResp->res, "VLAN/VLANEnable", 2080 vlanEnable, "VLAN/VLANId", vlanId, 2081 "Links/RelatedInterfaces", 2082 relatedInterfaces)) 2083 { 2084 return; 2085 } 2086 2087 if (relatedInterfaces.size() != 1) 2088 { 2089 messages::arraySizeTooLong(asyncResp->res, 2090 "Links/RelatedInterfaces", 2091 relatedInterfaces.size()); 2092 return; 2093 } 2094 2095 std::string parentInterfaceUri; 2096 if (!json_util::readJson(relatedInterfaces[0], asyncResp->res, 2097 "@odata.id", parentInterfaceUri)) 2098 { 2099 messages::propertyMissing(asyncResp->res, 2100 "Links/RelatedInterfaces/0/@odata.id"); 2101 return; 2102 } 2103 BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri); 2104 2105 boost::system::result<boost::urls::url_view> parsedUri = 2106 boost::urls::parse_relative_ref(parentInterfaceUri); 2107 if (!parsedUri) 2108 { 2109 messages::propertyValueFormatError( 2110 asyncResp->res, parentInterfaceUri, 2111 "Links/RelatedInterfaces/0/@odata.id"); 2112 return; 2113 } 2114 2115 std::string parentInterface; 2116 if (!crow::utility::readUrlSegments( 2117 *parsedUri, "redfish", "v1", "Managers", "bmc", 2118 "EthernetInterfaces", std::ref(parentInterface))) 2119 { 2120 messages::propertyValueNotInList( 2121 asyncResp->res, parentInterfaceUri, 2122 "Links/RelatedInterfaces/0/@odata.id"); 2123 return; 2124 } 2125 2126 if (!vlanEnable) 2127 { 2128 // In OpenBMC implementation, VLANEnable cannot be false on 2129 // create 2130 messages::propertyValueIncorrect(asyncResp->res, "VLAN/VLANEnable", 2131 "false"); 2132 return; 2133 } 2134 2135 std::string vlanInterface = parentInterface + "_" + 2136 std::to_string(vlanId); 2137 crow::connections::systemBus->async_method_call( 2138 [asyncResp, parentInterfaceUri, 2139 vlanInterface](const boost::system::error_code& ec, 2140 const sdbusplus::message_t& m) { 2141 afterVlanCreate(asyncResp, parentInterfaceUri, vlanInterface, ec, 2142 m); 2143 }, 2144 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 2145 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", parentInterface, 2146 vlanId); 2147 }); 2148 2149 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 2150 .privileges(redfish::privileges::getEthernetInterface) 2151 .methods(boost::beast::http::verb::get)( 2152 [&app](const crow::Request& req, 2153 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2154 const std::string& ifaceId) { 2155 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2156 { 2157 return; 2158 } 2159 getEthernetIfaceData( 2160 ifaceId, 2161 [asyncResp, 2162 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2163 const std::vector<IPv4AddressData>& ipv4Data, 2164 const std::vector<IPv6AddressData>& ipv6Data, 2165 const std::vector<StaticGatewayData>& ipv6GatewayData) { 2166 if (!success) 2167 { 2168 // TODO(Pawel)consider distinguish between non 2169 // existing object, and other errors 2170 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2171 ifaceId); 2172 return; 2173 } 2174 2175 asyncResp->res.jsonValue["@odata.type"] = 2176 "#EthernetInterface.v1_9_0.EthernetInterface"; 2177 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 2178 asyncResp->res.jsonValue["Description"] = 2179 "Management Network Interface"; 2180 2181 parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data, 2182 ipv6GatewayData); 2183 }); 2184 }); 2185 2186 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 2187 .privileges(redfish::privileges::patchEthernetInterface) 2188 .methods(boost::beast::http::verb::patch)( 2189 [&app](const crow::Request& req, 2190 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2191 const std::string& ifaceId) { 2192 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2193 { 2194 return; 2195 } 2196 std::optional<std::string> hostname; 2197 std::optional<std::string> fqdn; 2198 std::optional<std::string> macAddress; 2199 std::optional<std::string> ipv6DefaultGateway; 2200 std::optional<nlohmann::json::array_t> ipv4StaticAddresses; 2201 std::optional<nlohmann::json::array_t> ipv6StaticAddresses; 2202 std::optional<nlohmann::json::array_t> ipv6StaticDefaultGateways; 2203 std::optional<std::vector<std::string>> staticNameServers; 2204 std::optional<nlohmann::json> dhcpv4; 2205 std::optional<nlohmann::json> dhcpv6; 2206 std::optional<bool> ipv6AutoConfigEnabled; 2207 std::optional<bool> interfaceEnabled; 2208 std::optional<size_t> mtuSize; 2209 DHCPParameters v4dhcpParms; 2210 DHCPParameters v6dhcpParms; 2211 // clang-format off 2212 if (!json_util::readJsonPatch( 2213 req, asyncResp->res, 2214 "DHCPv4", dhcpv4, 2215 "DHCPv6", dhcpv6, 2216 "FQDN", fqdn, 2217 "HostName", hostname, 2218 "IPv4StaticAddresses", ipv4StaticAddresses, 2219 "IPv6DefaultGateway", ipv6DefaultGateway, 2220 "IPv6StaticAddresses", ipv6StaticAddresses, 2221 "IPv6StaticDefaultGateways", ipv6StaticDefaultGateways, 2222 "InterfaceEnabled", interfaceEnabled, 2223 "MACAddress", macAddress, 2224 "MTUSize", mtuSize, 2225 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", ipv6AutoConfigEnabled, 2226 "StaticNameServers", staticNameServers 2227 ) 2228 ) 2229 { 2230 return; 2231 } 2232 // clang-format on 2233 if (dhcpv4) 2234 { 2235 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 2236 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 2237 v4dhcpParms.useDnsServers, "UseNTPServers", 2238 v4dhcpParms.useNtpServers, "UseDomainName", 2239 v4dhcpParms.useDomainName)) 2240 { 2241 return; 2242 } 2243 } 2244 2245 if (dhcpv6) 2246 { 2247 if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode", 2248 v6dhcpParms.dhcpv6OperatingMode, 2249 "UseDNSServers", v6dhcpParms.useDnsServers, 2250 "UseNTPServers", v6dhcpParms.useNtpServers, 2251 "UseDomainName", 2252 v6dhcpParms.useDomainName)) 2253 { 2254 return; 2255 } 2256 } 2257 2258 // Get single eth interface data, and call the below callback 2259 // for JSON preparation 2260 getEthernetIfaceData( 2261 ifaceId, 2262 [asyncResp, ifaceId, hostname = std::move(hostname), 2263 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 2264 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 2265 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 2266 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 2267 ipv6StaticDefaultGateway = std::move(ipv6StaticDefaultGateways), 2268 staticNameServers = std::move(staticNameServers), 2269 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize, 2270 ipv6AutoConfigEnabled, v4dhcpParms = std::move(v4dhcpParms), 2271 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 2272 const bool& success, const EthernetInterfaceData& ethData, 2273 const std::vector<IPv4AddressData>& ipv4Data, 2274 const std::vector<IPv6AddressData>& ipv6Data, 2275 const std::vector<StaticGatewayData>& ipv6GatewayData) { 2276 if (!success) 2277 { 2278 // ... otherwise return error 2279 // TODO(Pawel)consider distinguish between non 2280 // existing object, and other errors 2281 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2282 ifaceId); 2283 return; 2284 } 2285 2286 if (dhcpv4 || dhcpv6) 2287 { 2288 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, 2289 asyncResp); 2290 } 2291 2292 if (hostname) 2293 { 2294 handleHostnamePatch(*hostname, asyncResp); 2295 } 2296 2297 if (ipv6AutoConfigEnabled) 2298 { 2299 handleSLAACAutoConfigPatch(ifaceId, *ipv6AutoConfigEnabled, 2300 asyncResp); 2301 } 2302 2303 if (fqdn) 2304 { 2305 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 2306 } 2307 2308 if (macAddress) 2309 { 2310 handleMACAddressPatch(ifaceId, *macAddress, asyncResp); 2311 } 2312 2313 if (ipv4StaticAddresses) 2314 { 2315 // TODO(ed) for some reason the capture of 2316 // ipv4Addresses above is returning a const value, 2317 // not a non-const value. This doesn't really work 2318 // for us, as we need to be able to efficiently move 2319 // out the intermedia nlohmann::json objects. This 2320 // makes a copy of the structure, and operates on 2321 // that, but could be done more efficiently 2322 nlohmann::json::array_t ipv4Static = *ipv4StaticAddresses; 2323 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp); 2324 } 2325 2326 if (staticNameServers) 2327 { 2328 handleStaticNameServersPatch(ifaceId, *staticNameServers, 2329 asyncResp); 2330 } 2331 2332 if (ipv6DefaultGateway) 2333 { 2334 messages::propertyNotWritable(asyncResp->res, 2335 "IPv6DefaultGateway"); 2336 } 2337 2338 if (ipv6StaticAddresses) 2339 { 2340 handleIPv6StaticAddressesPatch(ifaceId, *ipv6StaticAddresses, 2341 ipv6Data, asyncResp); 2342 } 2343 2344 if (ipv6StaticDefaultGateway) 2345 { 2346 handleIPv6DefaultGateway(ifaceId, *ipv6StaticDefaultGateway, 2347 ipv6GatewayData, asyncResp); 2348 } 2349 2350 if (interfaceEnabled) 2351 { 2352 setDbusProperty(asyncResp, "xyz.openbmc_project.Network", 2353 sdbusplus::message::object_path( 2354 "/xyz/openbmc_project/network") / 2355 ifaceId, 2356 "xyz.openbmc_project.Network.EthernetInterface", 2357 "NICEnabled", "InterfaceEnabled", 2358 *interfaceEnabled); 2359 } 2360 2361 if (mtuSize) 2362 { 2363 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 2364 } 2365 }); 2366 }); 2367 2368 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 2369 .privileges(redfish::privileges::deleteEthernetInterface) 2370 .methods(boost::beast::http::verb::delete_)( 2371 [&app](const crow::Request& req, 2372 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2373 const std::string& ifaceId) { 2374 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2375 { 2376 return; 2377 } 2378 2379 crow::connections::systemBus->async_method_call( 2380 [asyncResp, ifaceId](const boost::system::error_code& ec, 2381 const sdbusplus::message_t& m) { 2382 afterDelete(asyncResp, ifaceId, ec, m); 2383 }, 2384 "xyz.openbmc_project.Network", 2385 std::string("/xyz/openbmc_project/network/") + ifaceId, 2386 "xyz.openbmc_project.Object.Delete", "Delete"); 2387 }); 2388 } 2389 2390 } // namespace redfish 2391